在使用Json.NET进行序列化期间为所有类添加自定义类型名称

时间:2014-06-11 20:11:31

标签: c# .net serialization json.net

我要求添加'类型'我使用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,因此SerializationBinder是不可能的。)

1 个答案:

答案 0 :(得分:5)

不,你没有遗漏任何明显的东西。尝试使用处理各种类的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"
  }
}