1
votes

Given a ListModel with multiple layers of stored information (arrays of elements stored within elements), is there a way to store the model and recall it later?

I've tried storing ListModel as a JSON string, but it doesn't keep track of child objects. For example, in the following snippet, the output tells me there is a "kids" object, but has no knowledge of "kid1" nor "kid2". Same goes for the "store" object, but no knowledge of "duck1" nor "duck2".

import QtQuick 2.9
import QtQuick.Window 2.2

Window {
    visible: true
    width: 640
    height: 480

    ListModel {
        id: listModel
        ListElement {
            name: "Parent"
            kids: [
                ListElement {kid: "kid1"},
                ListElement {kid: "kid2"}
            ]
        }
        ListElement {
            name: "store"
            ducks: [
                ListElement {duck: "duck1"},
                ListElement {duck: "duck2"}
            ]
        }

        Component.onCompleted: {
            var datamodel = []
            for (var i = 0; i < this.count; ++i)
                datamodel.push(this.get(i))
            console.log(JSON.stringify(datamodel))
        }
    }
}

Here is the output, which fails to show any information about the child objects. I would expect there to be "kid1" and "kid2" under the "Parent" object.

[
  {
    "kids": {
      "objectName": "",
      "count": 2,
      "dynamicRoles": false
    },
    "name": "Parent"
  },
  {
    "name": "store",
    "ducks": {
      "objectName": "",
      "count": 2,
      "dynamicRoles": false
    }
  }
]

Edit: I would expect the output to be more like this:

[
  {
    "name": "Parent",
    "kids": [
      {
        "kid": "kid1"
      },
      {
        "kid": "kid2"
      }
    ]
  },
  {
    "name": "store",
    "ducks": [
      {
        "duck": "duck1"
      },
      {
        "duck": "duck2"
      }
    ]
  }
]
2
May I ask what you mean by "store the model and recall it later?" If it's what I think I'm not sure stringifying model is the way to go. As for the properties missing in the output, what do you get when you stringify them directly (instead of stringifying the container object which holds those properties)? - misterFad
I have a ListModel which I dynamically change during runtime (using the .append() and .remove() methods) and I want those changes to save on closing the program, and be held until the next time I open the program. Normally I can easily do this with a Settings QML type, so what I'm doing is Stringifying the ListModel and storing the string into the Settings type... however, there is this issue: the child objects ('kids' and 'ducks' in this example) do not end up in the stringified JSON, so I can't retrive that data next time I open the app and parse the data. - Tyler M
If I call console.log(JSON.stringify(listModel.get(0).kids)) then I get {"objectName":"","count":2,"dynamicRoles":false} which shows that the 'kids' array is being read as an object... most unfortunate. Trying console.log(JSON.stringify(listModel.get(0).kids.get(0))) is better, it at least gives {"kid":"kid1"} but creating a recursive loop for the entire tree... is this good practice if the tree is 10-layers deep? That's an exponentially growing for loop if it's 10 layers. - Tyler M
Indeed the deeper is the tree/hierarchy the longer it will take to serialize the whole object. And this is not specific to JSON.stringify... More info in my upcoming answer. - misterFad
you have to loop deep down Arrays to get its content with QML/JS tools ... - Mohammad Kanan

2 Answers

2
votes

It seems you want to save the current state of your model when application is closed and restore it when application is started again. Definitely ability to serialize and deserialize model is a must here.

[1/2] Qml-only based implementation

  1. Use this serializer from this SO answer, assuming you don't mind binding to third-party licenses (this one is totally free and the code seems reliable but I haven't tried or tested it). Then use JSON.parse to recreate objects from their string version.
  2. Craft your own (de)serializer and make sure it's tightly closed to the actual model you're working with. For instance in case not all of the properties from an object in your model need to be backuped/restored, then it might be wiser to directly look for the properties you need and only play with them. This might considerably lower the overhead cost of searching through hierarchy of properties but you'll endup with a very specific (de)serializer which is not reusable across projects.

[2/2] Qml/C++ implementation

Time permitting, another solution would be to design the model on C++ side and expose it as is or as QVariant to Qml code. Then (de)serialization will be entrusted to C++ code. The pros are: code is cleaner and I/O operations are faster. Cons are: it needs more time to be implemented and would be unnecessarily complicated in my opinion if the overall application is not feature-demanding.

1
votes

With the help of misterFad's link, I created a serialize() function that is specific to my application. Posting the code to spark intuition in others potentially stuck on the problem.

import QtQuick 2.9
import QtQuick.Window 2.2

Window {
    visible: true
    width: 640
    height: 480


    function serialize(object) {
        var model = object
        var totalText = "["

        for(var i = 0; i < model.count; i++) {
            var element = model.get(i)
            totalText += "{\"name\": \"" + element.name + "\""

            if(element.kids.count) totalText += ",\"kids\": " + serialize(element.kids)
            totalText += "}" + (i < model.count-1 ? ",": "")
        }

        return totalText + "]"
    }

    ListModel {
        id: listModel
        ListElement {
            name: "dad"
            kids: [
                ListElement {
                    name: "kid1";
                    kids: []
                },
                ListElement {
                    name: "kid2";
                    kids: [
                        ListElement {name: "grandkid1"; kids: []},
                        ListElement {name: "grandkid2"; kids: []}
                    ]
                }
            ]
        }
        ListElement {
            name: "store"
            kids: [
                ListElement {name: "duck1"; kids: []},
                ListElement {name: "duck2"; kids: []}
            ]
        }
        Component.onCompleted: console.log(serialize(listModel))
    }
}

output JSON is:

[
  {
    "name": "dad",
    "kids": [
      {
        "name": "kid1"
      },
      {
        "name": "kid2",
        "kids": [
          {
            "name": "grandkid1"
          },
          {
            "name": "grandkid2"
          }
        ]
      }
    ]
  },
  {
    "name": "store",
    "kids": [
      {
        "name": "duck1"
      },
      {
        "name": "duck2"
      }
    ]
  }
]