Skip to content
This repository has been archived by the owner on May 16, 2022. It is now read-only.

Deserializing type object with generated serializer #52

Open
danfrias opened this issue Feb 14, 2018 · 4 comments
Open

Deserializing type object with generated serializer #52

danfrias opened this issue Feb 14, 2018 · 4 comments

Comments

@danfrias
Copy link

First of all, thank you for this fantastic library. It has shown huge improvements for me over other libraries I've tried.

I am in the process of converting a Unity project that currently uses LitJson to use Utf8Json, due to big garbage creation and memory savings my testing has shown Utf8Json provides. One of the requirements of my project is that it must be compatible with existing JSON files as input, which means I can't use a solution that requires I re-serialize or modify the input data structure in any way. Because my Unity build uses IL2CPP, I am using the Universal Code Generator utility, but I have a couple of questions.

  1. I'm unclear how I would, or if its even possible to, deserialize to a generic object. Right now, when I run the RegisterAndSetAsDefault function to register my Generated Resolver, I use the following line:

Utf8Json.Resolvers.CompositeResolver.RegisterAndSetAsDefault (Utf8Json.Resolvers.GeneratedResolver.Instance, Utf8Json.Resolvers.BuiltinResolver.Instance);

The second parameter is what I found I needed to add to deserialize primitive objects. However, if I add the Standard Resolver as a parameter, as is shown in the example usage of the Universal Code Generator utility, I receive the same error at runtime on an iOS device as if I don't pre generate code, which is the NotSupportedException regarding System.Reflection.Emit.

Is there a different parameter I have to add in order to support the object type, or do I have to modify my generated resolver class to accommodate it? If so, how so?

  1. Some of the objects that are currently in the JSON files I am attempting to deserialize use a custom deserializer to deserialize different objects, in the same array, based on the first value that was stored in a "type" field. For example, an example JSON may look like this:
[
   {
      "___TYPE" : "DisplayAction",
      "image" : "testimage.png",
      "name" : "CatPic",
      "enabled" : false
   },
   {
      "___TYPE" : "PlaySoundAction",
      "sound" : "meow.wav",
      "name" : "meow",
      "duration" : 2.2,
      "delay" : 1.0,
      "enabled" : false,
      "sequence" : 
      [
         {
            "___TYPE" : "SoundSequence",
            "onStart" : null
            "onFinish" : null
            "times" : 2
         }
      ]
   }
]

The custom deserializer deserializes each of these objects into their respective types; in this case, it would be a DisplayAction object and a PlaySoundAction object. Is there a way to deserialize something like this?

Within the Unity Editor (I can't deserialize to Object yet on iOS because of my previous question), if I simply deserialize this into a List of type Object, the result that I have seen is of type Dictionary<string, object>. Is there a way, after deserialization, to then convert this to the final object type, such as (from the example above) DisplayAction or SoundAction?

Thank you for your help.

@neuecc
Copy link
Owner

neuecc commented Feb 15, 2018

You can't add StandardResolver for IL2CPP.
Sorry, I've missed to create PrimitiveObjectResolver, but currently you can add PrimitiveObjectFormatter it allows object -> json, json -> obejct serialization.

Utf8Json.Resolvers.CompositeResolver.RegisterAndSetAsDefault(
    new[] { PrimitiveObjectFormatter.Default },
    new[] { Utf8Json.Resolvers.GeneratedResolver.Instance, Utf8Json.Resolvers.BuiltinResolver.Instance });

for the performance, you can write custom formatter?

public class CustomObjectResolver : IJsonFormatter<object>
{
    static readonly IJsonFormatter<DisplayAction> displayActionFormatter = new DisplayActionFormatter();
    // or other formatters...

    public void Serialize(ref JsonWriter writer, object value, IJsonFormatterResolver formatterResolver)
    {
        throw new NotImplementedException(); // if serialize support, hmn....
    }

    public object Deserialize(ref JsonReader reader, IJsonFormatterResolver formatterResolver)
    {
        // throw new NotImplementedException();

        if (reader.GetCurrentJsonToken() == JsonToken.BeginObject)
        {
            var savedReader = reader; // copy struct(save offset index)

            // if always first property is ___TYPE, can read immediately
            if (reader.ReadPropertyName() == "___TYPE")
            {
                var typeName = reader.ReadString();

                switch (typeName)
                {
                    case "DisplayAction":
                        // pass savedReader.
                        return displayActionFormatter.Deserialize(ref savedReader, formatterResolver);
                    // case "": ....
                    default:
                        break;
                }
            }
        }

        // fallback, use primitiveobjectformatter.
        return PrimitiveObjectFormatter.Default.Deserialize(ref reader, formatterResolver);
    }
}

@danfrias
Copy link
Author

Thank you very much for your response. I have a question about number 2. When I implement this code to create a custom formatter for object, I get the following error:

JsonParsingException: expected:'String Begin Token', actual:'{', at offset:1654

This occurs on the the line:

if (reader.ReadPropertyName() == "___TYPE")

Any ideas what could be causing this?

@neuecc
Copy link
Owner

neuecc commented Feb 20, 2018

sorry, I've forget while(ReadIsInObject).

public class CustomObjectFormatter : IJsonFormatter<object>
{
    static readonly IJsonFormatter<DisplayAction> displayActionFormatter = new DisplayActionFormatter();
    // or other formatters...

    public void Serialize(ref JsonWriter writer, object value, IJsonFormatterResolver formatterResolver)
    {
        throw new NotImplementedException(); // if serialize support, hmn....
    }

    public object Deserialize(ref JsonReader reader, IJsonFormatterResolver formatterResolver)
    {
        if (reader.GetCurrentJsonToken() == JsonToken.BeginObject)
        {
            var count = 0;
            while (reader.ReadIsInObject(ref count))
            {
                var savedReader = reader; // copy struct(save offset index)

                // if always first property is ___TYPE, can read immediately
                if (reader.ReadPropertyName() == "___TYPE")
                {
                    var typeName = reader.ReadString();

                    switch (typeName)
                    {
                        case "DisplayAction":
                        // pass savedReader.
                         return displayActionFormatter.Deserialize(ref savedReader, formatterResolver);
                        // case "": ....
                        default:
                            break;
                    }
                }
            }
        }

        // fallback, use primitiveobjectformatter.
        return Utf8Json.Formatters.PrimitiveObjectFormatter.Default.Deserialize(ref reader, formatterResolver);
    }
}

@danfrias
Copy link
Author

danfrias commented Feb 23, 2018

Thank you again, you have been a great help and I have made a lot of progress. I do have a follow-up question about the custom deserializer.

If I deserialize to an object, and it comes out as an object with a specific type of Dictionary<string, object>, is there a way to then convert selected objects within that to a specific type? I have some instances where objects are deeply nested in and I don't necessarily want them to be deserialized all up front. I want to be able to take some objects out of the dictionary and, through a second pass, convert them from Dictionary<string, object> to the actual specific type, such as DisplayAction (from the example above).

With the current library that is in use in my project, which is LitJson, there is an option to deserialize to its internal format, JsonData, and then further deserialize subsections of that again to more specific types. Is that possible with Utf8Json? In other words, once I deserialize to can I then re-deserialize the resulting objecting to a specific class?

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

No branches or pull requests

2 participants