【问题标题】:Adding a custom type name to all classes during serialisation with Json.NET在使用 Json.NET 进行序列化期间向所有类添加自定义类型名称
【发布时间】:2014-08-02 00:43:51
【问题描述】:

我需要为使用 Json.Net 序列化的每个对象添加一个“类型”属性。我知道 Json.Net 已经支持这个开箱即用,但在我的情况下,类型名称需要排除程序集,并且属性的名称必须是可配置的(两者都不支持)。

我目前有这个:

public class TypeConverter : JsonConverter
{
    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serialiser)
    {
        JObject jObject = JObject.FromObject(value, serializer);
        jObject.AddFirst(new JProperty("myCustomTypePropertyName"), value.GetType().Name);
        jObject.WriteTo(writer);
    }

    public override void ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serialiser)
    {
        throw new NotImplementedException();
    }

    public override bool CanConvert(Type objectType)
    {
        return objectType.IsClass;
    }
}

这适用于正在序列化的外部类型,但不幸的是,嵌套对象没有调用转换器。如果我将序列化程序添加到 JObject.FromObject 调用中,那么当它尝试重新输入外部类型的转换器时,我会得到一个自引用循环异常。

我可以解决这个问题的唯一方法是手动反射和迭代每个级别的属性并使用序列化器参数对其进行序列化,但它非常难看,甚至在考虑性能之前也是如此。

我会很感激这方面的帮助;我希望我遗漏了一些明显的东西。

(注意:我运行的是 .NET 3.5,所以 SerializationBinders 是不可能的。)

【问题讨论】:

    标签: c# .net serialization json.net


    【解决方案1】:

    不,你没有遗漏任何明显的东西。由于您已经看到的原因,尝试使用处理各种类的JsonConverter 来做到这一点将会有问题。 JsonConverters 最适合处理特定类型;他们不太擅长概括。 幸运的是,有一种方法可以使用自定义 IContractResolver 来做您想做的事情。

    合约解析器允许我们在属性级别对各种类应用某些序列化行为。这个想法是让解析器在每个类上伪造一个额外的“类型名称”属性(或任何您想要调用的属性)并安装相应的IValueProvider 以在序列化每个类时为该属性提供值目的。 (这样,序列化程序永远不会知道该属性并不真正存在。)

    创建解析器的最简单方法是从 Json.Net 附带的 DefaultContractResolver 派生它。从那里我们只需要重写 CreateProperties() 方法并将我们的假属性注入从基类返回的列表中。

    这里是解析器和值提供者的代码:

    class CustomResolver : DefaultContractResolver
    {
        private string customTypePropertyName;
        private IValueProvider valueProvider = new SimpleTypeNameProvider();
    
        public CustomResolver(string customTypePropertyName)
        {
            this.customTypePropertyName = customTypePropertyName;
        }
    
        protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
        {
            IList<JsonProperty> props = base.CreateProperties(type, memberSerialization);
    
            if (type.IsClass && type != typeof(string))
            {
                // Add a phantom string property to every class which will resolve 
                // to the simple type name of the class (via the value provider)
                // during serialization.
                props.Insert(0, new JsonProperty
                {
                    DeclaringType = type,
                    PropertyType = typeof(string),
                    PropertyName = customTypePropertyName,
                    ValueProvider = valueProvider,
                    Readable = true,
                    Writable = false
                });
            }
    
            return props;
        }
    
        class SimpleTypeNameProvider : IValueProvider
        {
            public object GetValue(object target)
            {
                return target.GetType().Name;
            }
    
            public void SetValue(object target, object value)
            {
                return;
            }
        }
    }
    

    要使用解析器,请创建一个实例并通过 JsonSerializerSettings 对象将其传递给序列化器。这是一个简短的演示:

    class Program
    {
        static void Main(string[] args)
        {
            Person p = new Person
            {
                Id = 2,
                Name = "Peter",
                Employer = new Company
                {
                    Id = 5,
                    Name = "Initech"
                }
            };
    
            JsonSerializerSettings settings = new JsonSerializerSettings
            {
                ContractResolver = new CustomResolver("MyTypeName"),
                Formatting = Formatting.Indented
            };
    
            string json = JsonConvert.SerializeObject(p, settings);
    
            Console.WriteLine(json);
        }
    }
    
    class Person
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public Company Employer { get; set; }
    }
    
    class Company
    {
        public int Id { get; set; }
        public string Name { get; set; }
    }
    

    输出:

    {
      "MyTypeName": "Person",
      "Id": 2,
      "Name": "Peter",
      "Employer": {
        "MyTypeName": "Company",
        "Id": 5,
        "Name": "Initech"
      }
    }
    

    【讨论】:

    • 理想 - 完全错过了合同解析器。我将 jsonconverter 视为修改给定类型的序列化的唯一方法。非常感谢。
    • 没问题。很高兴我能帮上忙。
    • 呃,希望我在几个小时前看到这个。顺便说一句,您是否知道如何扭转这个过程?转换器在反序列化时也存在同样的问题(它们基本上从流程中删除了 newtonsoft),并且合约解析器不用于反序列化。我希望(并且仍在寻找)api中的某个点,json.net问我“这是一个JsonObject,给我它所需的类型和属性设置器”。到目前为止,我所看到的只是使用通用反序列化,然后自己解析出对象图,这很糟糕。
    • @Will 使用自定义的 SerializationBinder,参见stackoverflow.com/questions/11099466/…
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-03-07
    • 1970-01-01
    • 2013-07-15
    • 1970-01-01
    相关资源
    最近更新 更多