rpgmaker / NetJSON

Faster than Any Binary? Benchmark: http://theburningmonk.com/2014/08/json-serializers-benchmarks-updated-2/
MIT License
225 stars 29 forks source link

NetJSONKnownType doesn't work #231

Closed thecodrr closed 1 year ago

thecodrr commented 3 years ago

Okay, I just tried NetJSON and it is not serializing Interface properties properly. Here is my data structure:

public class User
{
      [DataMember(Name = "appData")]
      public AppData AppData { get; set; }
}
public class AppData
{
     [DataMember(Name = "subscription")]
     public ISubscription Subscription { get; set; }
}
[NetJSON.NetJSONKnownType(typeof(Subscription))]
public interface ISubscription
{
        string OrderId { get; set; }
        string Ref { get; set; }
        bool IsRecurring { get; set; }
        DateTime Expiry { get; set; }
}
public class Subscription : ISubscription
{
        [IgnoreDataMember]
        public string OrderId { get; set; }

        [IgnoreDataMember]
        public string Ref { get; set; }

        [DataMember(Name = "isRecurring")]
        public bool IsRecurring { get; set; }

        [DataMember(Name = "expiry")]
        public DateTime Expiry { get; set; }

        [DataMember(Name = "isTrial")]
        public bool IsTrial { get; set; }
 }

Now when I try to serialize the User object using:

var user = new User() { /* initialize appropriately */ };
NetJSON.NetJSON.Serialize(user);

It gives me this JSON:

{
   "appData":{
      "subscription":{
         "Expiry":"\/Date(15992888338610000)\/"
      }
   }
}

I have overriden CanSerialize and SerializeAs to respect DataMember and IgnoreDataMember attributes appropriately.

In the Subscription object, IsTrial = true and IsRecurring = false which is never serialized. I don't know what is the issue here. Also, Expiry key should be expiry.

Hope you can help.

rpgmaker commented 3 years ago

Hi,

Can you enable NetJson.IncludeTypeInformation = true

/// <summary>
    /// Enable including type information for serialization and deserialization
    /// </summary>
    public bool IncludeTypeInformation { get; set; }
rpgmaker commented 3 years ago

In regards to CanSerialize and SerializeAs. SerializeAs is used for object type serialization. And can canSerialize only allows you to control was is visible for serialization

rpgmaker commented 3 years ago

The type Information flag will let it include the type details to know how to serialize the data itself.

thecodrr commented 3 years ago

@rpgmaker Okay, IncludeTypeInformation = true made it work as expected however there is this unnecessary $type key. I am sending this data over the network to be consumed by a JS Web App. I could simply ignore it of course but is there a way to filter it out?

rpgmaker commented 3 years ago

There currently not a way to filter it out as when you get the data back, it will not know what type to use to deserialize. The only viable solution is to make a custom solution on your side that will use a number and based on that number you will use it to determine what type to call deserialization with as you don't need to use generic deserialization methods.

All this will need to be in your custom wrapper class.

thecodrr commented 3 years ago

@rpgmaker How about a TypeMap kind of? I can specify to it which interface is implemented by which class and it can use that information.

It might add some lookup time but as it will be optional, why not?

rpgmaker commented 3 years ago

There is one here. I did not document it and have not really tried to use it much. NetJSON.RegisterTypeSerializer . It will need you to do your custom serialization and return a full valid json. It will get compiled into the serialization itself. So the cost of look up only happens once per type and will perform based on the static method you register.

    /// <summary>
    /// Register serializer primitive method for <typeparamref name="T"/> when object type is encountered
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="serializeFunc"></param>
    public static void RegisterTypeSerializer<T>(Func<T, string> serializeFunc)
rpgmaker commented 3 years ago

There is also one for deserialization too.

thecodrr commented 3 years ago

@rpgmaker Okay so I tried making it work like:

NetJSON.NetJSON.RegisterCustomTypeSerializer<ISubscription>(NetJSONHelper.CustomTypeSerializer);

//NetJSONHelper.cs
public static void CustomTypeSerializer<T>(T obj, StringBuilder builder, NetJSON.NetJSONSettings settings)
{
            if (obj is ISubscription)
            {
                builder.Append(NetJSON.NetJSON.Serialize<Subscription>(obj as Subscription));
            }
}

But builder.Append replaces the whole string. How do I make it actually append?

rpgmaker commented 3 years ago

What do you mean by replace whole string. Do you mean it does not preserve the property that hold subscription?

I will have to test it again as this was originally intended for object serialization until I adapted it. I will expect thr behavior for it to simple just add to existing output.

thecodrr commented 3 years ago

@rpgmaker It doesn't add to the existing output.

Before builder.Append, builder holds this:

{"appData":{"subscription":}

After:

{"expiry":"\/Date(15992888338610000)\/","isTrial":true}{"expiry":"\/Date(15992888338610000)\/","isTrial":true}

And the overall result becomes:

"{\"expiry\":\"\\/Date(15992888338610000)\\/\",\"isTrial\":true}{\"expiry\":\"\\/Date(15992888338610000)\\/\",\"isTrial\":true}}}"

I have no idea why its adding it twice.

rpgmaker commented 3 years ago

Interesting. One thing to point out is that current implementation is mostly based on dealing with object as mentioned previously. So it will not deal with writing out the property/field name too. As for the duplicate and overwrite, that looks like a bug. I will try to fix that this weekend if time permits.

But the implementation will still most likely not handle the naming portion because it will take time to embed it to replace the il emitted code since it will require generating code that will need to do some kind of reflection to use the method as it is.

thecodrr commented 3 years ago

So it will not deal with writing out the property/field name too I am not sure I quite understood what you mean.

The CustomTypeSerializer function is simply using NetJSON's own Serialize function so why is property/field name going to be an issue?

As for the duplicate and overwrite, that looks like a bug. I will try to fix that this weekend if time permits. Cool.

thecodrr commented 3 years ago

@rpgmaker as a side question, is there a way to give NetJSON a default settings? I see there is NetJSONSettings.CurrentSettings but it is not respected when I call NetJSON.Serialize.

Also, why do we not have async implementations? Is there a reason for this? Is wrapping the Serialize/Deserialize in Task.Run enough?

rpgmaker commented 3 years ago

In regards to serialize. If you look at the current code, the starting point checks if method is registered and return by calling the custom method. But if it is not then it uses the default serialize logic which is responsible for not just serialization but also adding the name of the field or property.

Since I am emitting code and saving it to dynamic assembly in memory, I will need to bypass the portion of the code to make it to honor the custom method at the right point in the call stack.

If i remember, the default setting is kinda of static and is not meant to be used as it is which is why i have the option to pass yours. All the methods that controls the default settings are deprecated, and call them will update the default settings.

Wrapping in task.run should be enough as long as you are using your own settings object. The code is very thread safe for most operations.

rpgmaker commented 3 years ago

Is this still an issue?

rpgmaker commented 3 years ago

I am working on a solution to make this more useful for custom serializer types, so NETJson can be extended easily.

rpgmaker commented 3 years ago

So I just verified that the custom serialization actually does work as expected. The problem with the replacement occurs when you try to call NetJSON.Serialize inside the custom serialize which might be causing an issue with the generator.

As shown below, it yield the expected result in both scenario

With custom serializer only and manual building of json.

image

With custom serializer/deserializer and manual building of json

image

image

rpgmaker commented 3 years ago

@thecodrr , Let me know if the above makes sense to you.

rpgmaker commented 1 year ago

Closing issue. Reopen if still needed @thecodrr