问题描述
我发现自己经常这样做.我有一堂课,看起来像这样:
public class Foo
{
public SomeEnum SomeValue { get; set; }
public SomeAbstractBaseClass SomeObject { get; set; }
}
我需要做的是基于SomeValue
中的值反序列化从SomeAbstractBaseClass
派生的 specfic 类.因此,我要做的是在整个类上放置一个JsonConverterAttribute
,然后编写一个从JsonConverter
派生的自定义转换器,该转换器将在其ReadJson
中,首先检查SomeValue
,然后具有一些将SomeObject
反序列化为a的逻辑具体类别.这可行,但是有点烦人.唯一真正需要特殊处理的部分是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
枚举-因为可能尚未读取. >
因此,我首先想提出几个替代设计.由于Json.NET本质上是协定序列化程序,因此如果多态对象本身传达其类型信息而不是父容器对象,则使用起来会更容易.因此,您可以:
-
将多态类型枚举沿.
-
通过设置
JsonSerializerSettings.TypeNameHandling
到TypeNameHandling.Auto
.
话虽如此,您可以通过 ="nofollow noreferrer"> JsonConverter
,将容器类Foo
的JSON读入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);
}
}
I find myself doing this a lot. I have a class that looks something like this:
public class Foo
{
public SomeEnum SomeValue { get; set; }
public SomeAbstractBaseClass SomeObject { get; set; }
}
And what I need to do is deserialize a specfic class derived from SomeAbstractBaseClass
based on the value in SomeValue
. So what I do is put a JsonConverterAttribute
on the whole class and then write a custom converter derived from JsonConverter
that will in its ReadJson
, first examine SomeValue
and then have some logic to deserialize SomeObject
to a specific class. This works, but it's kind of annoying. The only part that really needs special handling is the SomeObject
property, but I have to put the converter at the higher level of the class and have my converter be responsible for populating all the other members of Foo
(i.e. SomeValue
, but you could imagine if you had lots of other properties that were fine with the default deserialization behavior). This could be avoided if there was only some way to get access to the parent object (or at least some property or properties from it) in the ReadJson
method of JsonConverter
. But there doesn't seem to be any way to do that. So if I could do something like:
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
}
}
There is the very suggestively named existingValue
parameter, but it always seems to be null? Is there a better way to do this?
According to the JSON specification, a JSON object is "an unordered set of name/value pairs", so trying to access the parent's SomeValue
enum while reading an instance of SomeAbstractBaseClass
isn't guaranteed to work -- as it might not have been read yet.
So, I'd first like to suggest a couple of alternative designs. Since Json.NET is basically a contract serializer, it will be easier to use if the polymorphic object itself conveys its type information, rather than parent container objects. Thus you could either:
Move the polymorphic type enum into
SomeAbstractBaseClass
along the lines of Json.Net Serialization of Type with Polymorphic Child Object.Use Json.NET's built-in support for polymorphic types by setting
JsonSerializerSettings.TypeNameHandling
toTypeNameHandling.Auto
.
That being said, you can reduce your pain somewhat by, inside a JsonConverter
, reading the JSON for your container class Foo
into a JObject
, splitting out the polymorphic properties for custom handling, and using JsonSerializer.Populate
to fill in the remaining properties. You can even standardize this pattern by creating an abstract converter that does this for you, using a custom attribute to determine which properties to split out:
[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;
}
}
And then use it like:
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);
}
}
这篇关于将其他信息传递给JsonConverter的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!