Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Begin serialization without enclosing parent node? #303

Open
rcdailey opened this issue Jun 21, 2016 · 17 comments
Open

Begin serialization without enclosing parent node? #303

rcdailey opened this issue Jun 21, 2016 · 17 comments

Comments

@rcdailey
Copy link

I want to write my object to json or xml archive without the preliminary wrapper node around the root-level data. This situation is covered perfectly in this stack overflow question:

http://stackoverflow.com/questions/33726072/how-to-serialize-a-json-object-without-enclosing-it-in-a-sub-object-using-cereal

My sample code:

MyClass myclass;

std::ostringstream ss;
cereal::XMLOutputArchive archive(ss);
archive(myclass);

The above yields the wrapping object which I do not want (named "value0"). Per the SO answer linked previously, the solution is:

myclass.serialize(archive);

However, this will not work if there are separate save/load functions or other types of callbacks.

Is there a built-in mechanism to disable the root node on develop?

@rcdailey
Copy link
Author

rcdailey commented Jun 22, 2016

Also this solution does not work if myclass is const (in conjunction with output archives, which is normally valid).

@AzothAmmo
Copy link
Contributor

There's no built in mechanism for this but you could modify xml.hpp or json.hpp to achieve what you want. In the constructors for the output archives we start the root node, which you could selectively disable by adding some parameters to the options struct.

You may have problems loading because you won't be loading valid XML/JSON documents. You could likely modify the input archives to handle this as well.

@rcdailey
Copy link
Author

I think you are missing the idea here. I'm not suggesting we remove the root XML node. But rather, the 2nd child that is created. Example:

<value0>
  <value0>
    <value0>foo</value0>
    <value1>bar</value1>
  </value0>
</value0>

The 2nd child is value0 and unnecessary. It is only added because I do archive(myclass). Instead, I want the XML output like:

<value0>
  <value0>foo</value0>
  <value1>bar</value1>
</value0>

This isn't invalid XML.

@rcdailey
Copy link
Author

rcdailey commented Jul 7, 2016

Any thoughts @AzothAmmo ?

@AzothAmmo
Copy link
Contributor

I haven't had time to look into this much yet, but I do understand what you want to do. Without thinking through all of the implications, I think this could potentially be implemented via a wrapper class that you would use something like:

ar( cereal::make_minimal( myData ) );

Which would cause myData to be serialized without an explicit node for itself as in your example.

I think this is a lot cleaner (implementation-wise) than adding new serialization function variants.

@rcdailey
Copy link
Author

I guess my point is, the current behavior seems undesirable. Is there a reason to have another root under the root you already add at the archive level? Isn't it redundant? I can't think of a single use case for it.

It seems like the latter example output I provided in an earlier reply should be the default behavior.

Unless the use case is to support minimal types passed directly to the archive? I can understand then, but doesn't seem like a practical scenario.

I like your idea of flexibility I am just questioning if the current behavior has valid use cases.

@AzothAmmo
Copy link
Contributor

It is the way it is because we need to support serializing multiple objects to the same archive.

@rcdailey
Copy link
Author

That's true! Good point. I'll look forward to your suggested solution.

On Mon, Jul 11, 2016, 6:45 PM Shane Grant notifications@github.com wrote:

It is the way it is because we need to support serializing multiple
objects to the same archive.


You are receiving this because you authored the thread.
Reply to this email directly, view it on GitHub
#303 (comment), or mute
the thread
https://github.com/notifications/unsubscribe/ABr6doe0DgCStNCOa4DTAUfcdT1H6WxMks5qUtWtgaJpZM4I7E-l
.

@rcdailey
Copy link
Author

So I am working on a make_minimal implementation, however I'm not sure how I should do it. I have a rough idea I'm trying to make work:

namespace cereal
{
   namespace detail
   {
      template<typename T>
      class minimal_wrapper
      {
      public:
         minimal_wrapper(T& obj)
            : m_obj(obj)
         {}

         template<typename Archive>
         std::string save_minimal(Archive const& ar)
         {
            // save here somehow
         }

         template<typename Archive>
         void load_minimal(Archive const& ar, std::string const& value)
         {
            // load here somehow
         }

      private:
         T& m_obj;
      };
   }

   template<typename T>
   detail::minimal_wrapper<T> make_minimal(T& obj)
   {
      return detail::minimal_wrapper<T>{obj};
   }
}

However, I'm not sure how I should implement save_minimal and load_minimal in the wrapper. Because the archive is const, I guess that means I can't use it (not even sure why it's passed in to begin with).

Do you have any ideas? I want to implement this externally first and see how it works, then I'm happy to do a PR.

@AzothAmmo
Copy link
Contributor

My idea was more along the lines of how NameValuePair and the traits for xxx_minimal work. Essentially the class would be a very thin wrapper that is detected by prologue/epilogue functions and sets up state appropriately.

Naming it make_minimal was not a good choice on my part. You can't directly use save or load minimal because these expect a single value to be serialized.

I'm a bit worried that there may be ways to break this mechanism through some combination of nesting it and using out of order loading.

@rcdailey
Copy link
Author

What this is effectively enabling is a "passthrough" to children, or a "make children into siblings" functionality.

In that case, wouldn't it be reasonable to make it function as if the intermediate class doesn't exist at all? The children will be treated as siblings to the parent that initiated its serialize function.

But honestly this isn't something we need to overgeneralize. I can't even make a good argument for using this functionality outside of the root level invocation to the archive.

Can't we just make it a simple on/off flag at the archive level?

MyObj myobj;
std::istringstream ss{"some json data"};
cereal::JSONInputArchive archive(ss, no_siblings); // 'no_siblings' could be an enumerator provided by cereal; one of many to add constraints to or change behavior of archives
archive(myobj);

Effectively this forces the serializable class (MyObj in this case) to write the root-level node. It is also an error to use an archive with this option set for more than 1 sibling object (you can detect this and throw for num args > 1.

Much simpler, less scenarios to consider. Similar to this, maybe a CRTP derived class that can be used to change this behavior:

cereal::SingleUseArchive<cereal::JSONInputArchive> archive(ss);
archive(myobj);
archive(mysecondobj); // throws

Food for thought...

@rcdailey
Copy link
Author

@AzothAmmo Would it be possible for you to help me come up with a solution to your make_minimal idea? I'm happy to help contribute a change, I just need some help getting started.

This is a huge problem right now for me so I need a solution.

arximboldi added a commit to arximboldi/lager that referenced this issue Oct 29, 2017
Before the responses would be a struct of the form:
```
   {
     "value0": ...the actual stuff...
   }
```

This is related to this issue:
   USCiLab/cereal#303

By crafting the response manually instead of defining the response as
a C++ type that is then serialized, we solve the problem.  It seems to
be less code in the end anyways :-)
@spavlenko
Copy link

spavlenko commented Nov 2, 2017

If you are using external serialize function, u also can fix that problem calling this function directly:

template <class  Archive>
    void serialize(Archive & ar, ReporRegistrationData& d)
    {
        ar(cereal::make_nvp("app_id", d.app_id),
            cereal::make_nvp("app_version", d.app_version),
            cereal::make_nvp("hw_id", d.hw_id),
            cereal::make_nvp("event_type", d.event_type),
        );
    }

And then for serialization:

    std::stringstream stream(std::ios_base::out | std::ios_base::binary);
    {
        cereal::JSONOutputArchive serializer(stream);
        cereal::serialize<cereal::JSONOutputArchive>(serializer, data);
    }

    const auto res = stream.str();

for deserialization (because cereal also doesn't wanna deserialize without enclosing parent node ):

     std::stringstream stream(std::ios_base::in | std::ios_base::out);
     stream.write(json.data(), json.size());

     cereal::JSONInputArchive deserializer(stream);
     cereal::serialize<cereal::JSONInputArchive>(deserializer, data);

Hope it will help somebody.

@LowCostCustoms
Copy link

LowCostCustoms commented Nov 8, 2017

Hi there. I wrote simple header that help to perform inline serialization (without versioning): https://github.com/LowCostCustoms/cereal-inline/blob/master/cereal_inline.hpp

@Devacor
Copy link

Devacor commented Mar 21, 2019

@LowCostCustoms is a versioning supported version hard to make? I took a look but immediately ran into a bit of trouble so thought I'd ask if anyone else has tried?

@wrightleft
Copy link

I'm running into this with serializing a std::vector. I get:

{
  "value0": [
    1,
    2,
    3
  ]
}

But I want:

[
  1,
  2,
  3
]

This was my first test with cereal, and I can't figure out how to get what I want. It might be back to rapidjson for me. :(

@uentity
Copy link
Contributor

uentity commented Oct 31, 2019

@wrightleft
Give you vector a meaningful name via make_nvp

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

7 participants