neuecc / Utf8Json

Definitely Fastest and Zero Allocation JSON Serializer for C#(NET, .NET Core, Unity, Xamarin).
MIT License
2.36k stars 267 forks source link

Properties not properly serialized when serializing polymorphic types in a List #148

Open mindcrash opened 5 years ago

mindcrash commented 5 years ago

Given the following type structure:

public abstract class Setting
{
   public string Name { get; set; }
   public abstract string Kind { get; }
}

public class TextSetting : Setting
{
   public override string Kind => "text";
   public string Value { get; set; }
}

When putting these in a list:

settings = new List<Setting>()
{
    new TextSetting(){ Name = "Alpha", Value = "a" }
}

And serializing them to a output stream:

JsonSerializer.SerializeAsync(context.Response.Body, settings, resolver)

The output is:

[{"name":"Alpha","kind":"text"}]

But I expect it to be:

[{"name":"Alpha", "value": "a", "kind":"text"}]

When serializing one item:

JsonSerializer.SerializeAsync(context.Response.Body, new TextSetting(){ Name = "Alpha", Value = "a" }, resolver)

The output is correct:

{"name":"Alpha", "value": "a", "kind":"text"}

So there is definitely a issue within the serializer responsible for serializing iterable types and how it deals with its content.

startewho commented 5 years ago

maybe you use the

settings = new List<TextSetting>()
{
    new TextSetting(){ Name = "Alpha", Value = "a" }
}

it will get the correct value

mindcrash commented 5 years ago

I know, but we have several objects with several types derived from Setting in that list. :)

For now I solved this with a custom formatter, but life would become much more easier if Utf8Json could fetch all properties residing in the object hierarchy within its own formatter, especially for one-way scenarios (e.g. objects serialized to the HTTP response stream)

xXMateus97Xx commented 5 years ago

I had the same problem some days ago.

I was changing JSON.NET by UTF8JSON in a NopCommerce project, since I use Redis for cache, it was needed to refactor the RedisCacheManager class.

The problem appeared on CategoryService, that returns a IList<Category> with items of type CategoryForCaching, as you can notice, this class inherit from Category.

The Category class is used to transfer data between application and database using EntityFramework, to avoid save navigation properties on cache the CategoryForCachingclass was created, since it will be a reference loop between objects.

After update the methods at RedisCacheManager, the exception throwed by CategoryForCaching navigation properties was being released and after tried to use the IgnoreDataMember attribute, the error was still happening, the problem took me some hours to be resolved.

The solution I found it was create a new IJsonFormatterResolver implementation to return a different resolver when the the implement the IEntityForCaching.

In JSON.NET when you have a collection, the serializer looks for all the visible properties in the class and you only need to do a custom resolver to deserialize the items, since it was not possible to know what the type it was used to generate the JSON.

Domenico-Pirozzi commented 4 years ago

I know, but we have several objects with several types derived from Setting in that list. :)

For now I solved this with a custom formatter, but life would become much more easier if Utf8Json could fetch all properties residing in the object hierarchy within its own formatter, especially for one-way scenarios (e.g. objects serialized to the HTTP response stream)

Hii @mindcrash , how did you implement a custom formatter to solve this issues.

I know this is an old thread, but I am learning about JSON serializers and I'd like to see you solution.

Thanks

thomai-d commented 4 years ago

@pippo46, there is a nongeneric API you can use:

JsonSerializer.NonGeneric.Serialize(c);

I found it in the docs: https://github.com/neuecc/Utf8Json#high-level-apijsonserializer

pavlalexandr commented 2 years ago

I know, but we have several objects with several types derived from Setting in that list. :) For now I solved this with a custom formatter, but life would become much more easier if Utf8Json could fetch all properties residing in the object hierarchy within its own formatter, especially for one-way scenarios (e.g. objects serialized to the HTTP response stream)

Hii @mindcrash , how did you implement a custom formatter to solve this issues.

I know this is an old thread, but I am learning about JSON serializers and I'd like to see you solution.

Thanks

Very simple thing:

public class PolymorphJsonFormatter<TBase> : IJsonFormatter<TBase>
    {
        public TBase Deserialize(ref JsonReader reader, IJsonFormatterResolver formatterResolver)
        {
            return formatterResolver.GetFormatter<TBase>().Deserialize(ref reader, formatterResolver);
        }

        public void Serialize(ref JsonWriter writer, TBase value, IJsonFormatterResolver formatterResolver)
        {
            JsonSerializer.NonGeneric.Serialize(value.GetType(), ref writer, value, formatterResolver);  
        }
    }
jhughes2112 commented 2 years ago

Bzzzt. None of these answers are correct for a polymorphic type. If you have the following:

class Base
{
}

class Derived : Base
{
    int x;
}

If you try to serialize an object and it's acting as the base class at the point-of-call, the output is only Base. If you serialize it as Derived, you get Derived properties. Serialization depends on what type the object is at the point of call, which is wrong.

Derived msg = new Derived() { x= 3 };
string correct = Utf8Json.JsonSerializer.ToJsonString(msg);
string incorrect = Utf8Json.JsonSerializer.ToJsonString((Base)msg);

If you use the NonGeneric.ToJsonString(msg) version, it will work regardless of the type at point of call. However, no matter how you serialize the properties, you never get any kind of TYPE information so the deserializer has no idea what type to create. Unless the code just "knows" what type to create, all you can do is bring it in as some kind of dynamic object, then try to figure out what to cast it to. I would say that polymorphism does not work with this package.

fastJSON does correctly handle polymorphism, but it's incredibly slow by comparison. Spent a couple of days switching over, only to find out it doesn't work. :-(