序列化对象的自定义 $type 值 [英] Custom $type value for serialized objects

查看:20
本文介绍了序列化对象的自定义 $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.

推荐答案

与其添加合成的 $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 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() 是在 .Net 4.0 中引入的,因此如果您使用的是旧版本的 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 中 TypeNameHandling 的警告Json 并确保清理传入类型以防止构建有害类型.

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 值的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆