Loki-Astari / ThorsMongo

C++ MongoDB API and BSON/JSON Serialization library
GNU General Public License v3.0
316 stars 72 forks source link

Deserializing to existing object with vectors/pointers? #64

Closed ripe909 closed 4 years ago

ripe909 commented 4 years ago

This is more of a question than a bug. Hopefully it makes sense.

If I have a pre-existing object instance which includes a nested tree of vectors and pointers (see my previous issue), can I deserialize to this existing object when some of the vector sizes and pointer locations must change because the serialized data is different than the existing object?

For example, the existing object before deserialization has a member vector of size 2, but the incoming deserialization requires this vector to grow to size 4. In my preliminary tests, this isn’t working, and the existing vector stays at size 2. Others values (ints) are updating correctly.

EDIT: OK, now I'm not even sure how to deserialize to an object if it is new. I don't see any examples for deserialization of objects in the docs.

Using the code from my previous example:

class level8_cont
{
public:
    std::array<int, 3> array9 = { 1, 2, 3 };
};
class level7
{
public:
    level7()
    {
        levels8.emplace_back();
    }
    std::vector<level8_cont> levels8;
};
class level6_cont
{
public:
    level6_cont()
    {
        nextLevel7 = new level7();
    }
    level7 *nextLevel7;
};
class level5
{
public:
    level5()
    {
        levels6.emplace_back();
    }
    std::vector<level6_cont> levels6;
};
class level4_cont
{
public:
    level4_cont()
    {
        nextLevel5 = new level5();
    }
    level5 *nextLevel5;
};
class level3
{
public:
    level3()
    {
        levels4.emplace_back();
    }
    std::vector<level4_cont> levels4;
};
class level2_cont
{
public:
    level2_cont()
    {
        nextLevel3 = new level3();
    }
    level3 *nextLevel3;
};
class level1
{
public:
    level1()
    {
        levels2.emplace_back();
    }

    std::vector<level2_cont> levels2;
};

ThorsAnvil_MakeTrait(level1, levels2);
ThorsAnvil_MakeTrait(level2_cont, nextLevel3);
ThorsAnvil_MakeTrait(level3, levels4);
ThorsAnvil_MakeTrait(level4_cont, nextLevel5);
ThorsAnvil_MakeTrait(level5, levels6);
ThorsAnvil_MakeTrait(level6_cont, nextLevel7);
ThorsAnvil_MakeTrait(level7, levels8);
ThorsAnvil_MakeTrait(level8_cont, array9);

using ThorsAnvil::Serialize::jsonExport;
using ThorsAnvil::Serialize::jsonImport;

// Use the export function to serialize
level1* levelTest;
levelTest = new level1();
std::cout << jsonExport(*levelTest) << "\n";

Assuming the incoming deserialized object does not have the same vector sizes as the existing object, should the deserialize for this object look like this? (Also assume the destructors of the classes existed to delete the objects pointed to by member pointers)

delete levelTest;
inputStream >> jsonImport(*levelTest);

Also, I want say a huge Thank You! for this library, it has been exactly what I needed.

Loki-Astari commented 4 years ago

When reading data in. If I remember correctly it should correctly increase the size of the vector to the size of the new object. But let me go and check the details.

ripe909 commented 4 years ago

I guess it would have to clear all vectors first, setting the vector size to zero, in case the incoming vector is also smaller than the existing.

Loki-Astari commented 4 years ago

You should not need to. I am pretty sure the code makes the vector the correct size depending on the incoming Json array.

Loki-Astari commented 4 years ago

OK. The part in the code that reads it is here:

https://github.com/Loki-Astari/ThorsSerializer/blob/master/src/Serialize/SerUtil.h#L193

The line:

            V&                  data = extractor.get(index);

Gets a reference to the object in the array we are about to read. For std::vector<T> this does:

https://github.com/Loki-Astari/ThorsSerializer/blob/master/src/Serialize/SerUtil.h#L312

            container.emplace_back();
            return container.back();

This emplaces an object at the back of the vector and returns a reference to it. The serialization code then reads data into the object that it just emplaced into the vector.

So: The code will not clear the array but should correctly append values (assuming they have a valid default constructor).

Does that help?

So it should work for an initially empty object. But if you do it over an existing object it will append to vectors thus extending them.

ripe909 commented 4 years ago

Yes, that helps, thanks! I am going to try clearing the vectors myself before deserializing, which should reconstitute them with the correct new values/size.

Loki-Astari commented 4 years ago

A simpler technique may be to use move semantics.

    // If your code looked like this:
    stream >> jsonImport(x);

    // You could do:
    MyTypeTheSameAsX    y;         // Create a new empty object.
    stream >> jsonImport(x);       // Stream into the new object (which should be clean)

    x = std::move(y);              // Move y into x (which should be effecient)
                                   // This will release the old structures automatically.
ripe909 commented 4 years ago

The vector clearing worked well, I just call clear on my top level vector which deletes the entire tree structure and then the deserialization creates the whole tree of vectors again.

Loki-Astari commented 4 years ago

OK. All turned out good. Closing the issue.