Custom $type value for serialized objects

╄→尐↘猪︶ㄣ 提交于 2020-01-13 13:50:14

问题


We're using Web API with Json.Net using TypeNameHandling = TypeNameHandling.Objects in our serializer settings. This works fine, but we use the type information only client-side, never for deserialization. Our serialized objects look like this:

{
    "$type": "PROJECTNAME.Api.Models.Directory.DtoName, PROJECTNAME.Api",
    "id": 67,
    "offices": [{
        "$type": "PROJECTNAME.Api.Models.Directory.AnotherDtoName, PROJECTNAME.Api",
        "officeName": "FOO"
    }]
},

I would like to customize the value in the $type property so it reads as:

{
    "$type": "Models.Directory.DtoName",
    "id": 67,
    "offices": [{
        "$type": "Models.Directory.AnotherDtoName",
        "officeName": "FOO"
    }]
},

I already have a contract resolver that inherits from CamelCasePropertyNamesContractResolver. I figure what I need to do is turn off TypeNameHandling and add a custom property myself. I'm 95% there:

protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
{
    var assemblyName = type.Assembly.GetName().Name;
    var typeName = type.FullName.Substring(assemblyName.Length + 1);

    var typeProperty = new JsonProperty()
    {
        PropertyName = "$type",
        PropertyType = typeof(string),
        Readable = true,
        Writable = true,
        ValueProvider = null // ????? typeName
    };

    var retval = base.CreateProperties(type, memberSerialization);
    retval.Add(typeProperty);
    return retval;
}

At this point I'm stuck with supplying the property's value.

I'm unsure that this is the correct approach because each of the ValueProvider types from Json.Net take a MemberInfo as a constructor parameter. I don't have a MemberInfo to supply as a parameter, so.... I'm stuck.

How do I add a custom $type value? Since I'm not doing deserialization in C# I will never need to convert the type information back into a type.


回答1:


Rather than adding a synthetic $type property, you should create a custom ISerializationBinder and override ISerializationBinder.BindToName. This method is called during serialization to specify the type information to emit when TypeNameHandling is enabled.

For instance, the following strips the assembly information as well as the PROJECTNAME.Api. portion of the namespace:

public class MySerializationBinder : ISerializationBinder
{
    const string namespaceToRemove = "PROJECTNAME.Api.";

    readonly ISerializationBinder binder;

    public MySerializationBinder() : this(new Newtonsoft.Json.Serialization.DefaultSerializationBinder()) { }

    public MySerializationBinder(ISerializationBinder binder)
    {
        if (binder == null)
            throw new ArgumentNullException();
        this.binder = binder;
    }

    #region ISerializationBinder Members

    public void BindToName(Type serializedType, out string assemblyName, out string typeName)
    {
        binder.BindToName(serializedType, out assemblyName, out typeName);
        if (typeName != null && typeName.StartsWith(namespaceToRemove))
            typeName = typeName.Substring(namespaceToRemove.Length);

        assemblyName = null;
    }

    public Type BindToType(string assemblyName, string typeName)
    {
        throw new NotImplementedException();
    }

    #endregion
}

Then you can serialize your DtoName object with it as follows:

var settings = new JsonSerializerSettings
{
    ContractResolver = new CamelCasePropertyNamesContractResolver(),
    SerializationBinder = new MySerializationBinder(),
    TypeNameHandling = TypeNameHandling.Objects,
};
var json = JsonConvert.SerializeObject(dto, Formatting.Indented, settings);

Notes:

  • Newtonsoft introduced ISerializationBinder in release 10.0.1 as a replacement to System.Runtime.Serialization.SerializationBinder, apparently because that type is missing in some versions of .Net core. If you are using a version of Json.NET that precedes 10.0.1 you will need to create a custom version of that instead.

    Note also that SerializationBinder.BindToName() was introduced in .Net 4.0, so if you are using an old version of Json.NET and an old version of .Net itself then this solution will not work.

  • As you are not doing deserialization in c# I simply threw an exception from BindToType(). But if someone were to implement BindToType(), they should take heed of the caution from TypeNameHandling caution in Newtonsoft Json and be sure to sanitize the incoming types to prevent construction of harmful types.



来源:https://stackoverflow.com/questions/49283251/custom-type-value-for-serialized-objects

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!