我发现自己经常这样做。我有一个类,看起来像这样:
public class Foo
{
public SomeEnum SomeValue { get; set; }
public SomeAbstractBaseClass SomeObject { get; set; }
}
我需要做的是基于SomeValue
中的值反序列化一个来自SomeAbstractBaseClass
的特定的类。所以我所做的是把一个JsonConverterAttribute
放在整个类上,然后写一个自定义的转换器,从JsonConverter
派生,将在其ReadJson
中,首先检查SomeValue
,然后有一些逻辑将SomeObject
反序列化到特定的类。这个方法有效,但有点烦人。真正需要特殊处理的唯一部分是SomeObject
属性,但我必须将转换器放在类的更高级别,并让我的转换器负责填充Foo
的所有其他成员(即SomeValue
,但您可以想象如果您有许多其他属性可以正常使用默认的反序列化行为)。如果在JsonConverter
的ReadJson
方法中只有某种方法可以访问父对象(或至少来自它的一些属性),则可以避免这种情况。但似乎没有办法做到这一点。如果我可以这样写:
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var parent = //...somehow access the parent or at least SomeValue
switch (parent.SomeValue)
{
case Value1:
serialized.Deserialize<SpecificType1>(reader);
break;
//... other cases
}
}
有一个非常有启发性的命名existingValue
参数,但它似乎总是为空?有更好的方法吗?
根据JSON规范,JSON对象是"一组无序的名称/值对",因此在读取SomeAbstractBaseClass
实例的同时尝试访问父对象的SomeValue
enum不能保证工作——因为它可能还没有被读取。
所以,我首先想建议几个可供选择的设计。由于Json。NET基本上是一个契约序列化器,如果多态对象本身传递它的类型信息,而不是父容器对象,使用起来会更容易。因此,您可以:
-
将多态类型枚举沿着Json的行移动到
SomeAbstractBaseClass
中。具有多态子对象的类型的网络序列化。 -
使用Json。. NET通过设置
JsonSerializerSettings.TypeNameHandling
为TypeNameHandling.Auto
来内置对多态类型的支持
话虽这么说,你可以减少你的痛苦在一定程度上通过,在JsonConverter
内,读取JSON为您的容器类Foo
到JObject
,分离出多态属性自定义处理,并使用JsonSerializer.Populate
来填充剩余的属性。您甚至可以通过创建一个抽象转换器(使用自定义属性来确定要拆分哪些属性)来标准化此模式:
[System.AttributeUsage(System.AttributeTargets.Property | System.AttributeTargets.Field, AllowMultiple = false)]
public sealed class JsonCustomReadAttribute : Attribute
{
}
public abstract class JsonCustomReadConverter : JsonConverter
{
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null)
return null;
var contract = serializer.ContractResolver.ResolveContract(objectType) as JsonObjectContract;
if (contract == null)
throw new JsonSerializationException("invalid type " + objectType.FullName);
var value = existingValue ?? contract.DefaultCreator();
var jObj = JObject.Load(reader);
// Split out the properties requiring custom handling
var extracted = contract.Properties
.Where(p => p.AttributeProvider.GetAttributes(typeof(JsonCustomReadAttribute), true).Count > 0)
.Select(p => jObj.ExtractProperty(p.PropertyName))
.Where(t => t != null)
.ToList();
// Populare the properties not requiring custom handling.
using (var subReader = jObj.CreateReader())
serializer.Populate(subReader, value);
ReadCustom(value, new JObject(extracted), serializer);
return value;
}
protected abstract void ReadCustom(object value, JObject jObject, JsonSerializer serializer);
public override bool CanWrite { get { return false; } }
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
public static class JsonExtensions
{
public static JProperty ExtractProperty(this JObject obj, string name)
{
if (obj == null)
throw new ArgumentNullException();
var property = obj.Property(name);
if (property == null)
return null;
property.Remove();
return property;
}
}
然后像这样使用:
public abstract class SomeAbstractBaseClass
{
}
public class Class1 : SomeAbstractBaseClass
{
public string Value1 { get; set; }
}
public class Class2 : SomeAbstractBaseClass
{
public string Value2 { get; set; }
}
public static class SomeAbstractBaseClassSerializationHelper
{
public static SomeEnum SerializedType(this SomeAbstractBaseClass baseObject)
{
if (baseObject == null)
return SomeEnum.None;
if (baseObject.GetType() == typeof(Class1))
return SomeEnum.Class1;
if (baseObject.GetType() == typeof(Class2))
return SomeEnum.Class2;
throw new InvalidDataException();
}
public static SomeAbstractBaseClass DeserializeMember(JObject jObject, string objectName, string enumName, JsonSerializer serializer)
{
var someObject = jObject[objectName];
if (someObject == null || someObject.Type == JTokenType.Null)
return null;
var someValue = jObject[enumName];
if (someValue == null || someValue.Type == JTokenType.Null)
throw new JsonSerializationException("no type information");
switch (someValue.ToObject<SomeEnum>(serializer))
{
case SomeEnum.Class1:
return someObject.ToObject<Class1>(serializer);
case SomeEnum.Class2:
return someObject.ToObject<Class2>(serializer);
default:
throw new JsonSerializationException("unexpected type information");
}
}
}
public enum SomeEnum
{
None,
Class1,
Class2,
}
[JsonConverter(typeof(FooConverter))]
public class Foo
{
[JsonCustomRead]
public SomeEnum SomeValue { get { return SomeObject.SerializedType(); } }
[JsonCustomRead]
public SomeAbstractBaseClass SomeObject { get; set; }
public string SomethingElse { get; set; }
}
public class FooConverter : JsonCustomReadConverter
{
protected override void ReadCustom(object value, JObject jObject, JsonSerializer serializer)
{
var foo = (Foo)value;
foo.SomeObject = SomeAbstractBaseClassSerializationHelper.DeserializeMember(jObject, "SomeObject", "SomeValue", serializer);
}
public override bool CanConvert(Type objectType)
{
return typeof(Foo).IsAssignableFrom(objectType);
}
}