本文介绍了序列化对象的自定义$ type值的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我们在序列化程序设置中使用TypeNameHandling = TypeNameHandling.Objects将Web API与Json.Net一起使用.这可以正常工作,但是我们仅在客户端使用类型信息,而不是在反序列化中使用.我们的序列化对象如下所示:

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"
    }]
},

我想自定义$type属性中的值,使其读为:

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"
    }]
},

我已经有一个继承自CamelCasePropertyNamesContractResolver的合同解析器.我想我需要做的是关闭TypeNameHandling并自己添加一个自定义属性.我在那里95%:

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.

我不确定这是正确的方法,因为Json.Net的每个ValueProvider类型都将MemberInfo作为构造函数参数.我没有没有提供MemberInfo作为参数,所以....我被卡住了.

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.

如何添加自定义$type值?由于我没有在C#中进行反序列化,因此我永远不需要将类型信息转换回类型.

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.

推荐答案

您应该创建自定义ISerializationBinder 并覆盖 ISerializationBinder.BindToName .序列化期间调用此方法以指定 TypeNameHandling 已启用.

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.

例如,以下内容剥离了程序集信息以及名称空间的PROJECTNAME.Api.部分:

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
}

然后,您可以使用它序列化DtoName对象,如下所示:

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);

注意:

  • Newtonsoft在 ISerializationBinder href ="https://github.com/JamesNK/Newtonsoft.Json/releases/tag/10.0.1" rel ="nofollow noreferrer">版本10.0.1 代替 System.Runtime.Serialization.SerializationBinder ,显然是因为某些版本的.Net核心.如果您使用的Json.NET版本低于10.0.1,则需要创建该版本的自定义版本.

  • 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.

还请注意, SerializationBinder.BindToName() ,因此,如果您使用的是Json.NET的旧版本和.Net本身的旧版本,则此解决方案将不起作用.

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.

由于您没有在c#中进行反序列化,因此我只是从 BindToType() .但是,如果有人要实施BindToType(),则应该注意Newtonsoft Json中 TypeNameHandling的警告 ,并确保对传入的类型进行清理以防止构造有害类型.

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.

这篇关于序列化对象的自定义$ type值的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

08-04 21:48