2015-04-22 77 views
7

我有一个类等的接口类型属性:使用自定义JsonConverter和TypeNameHandling在Json.net

public class Foo 
{ 
    public IBar Bar { get; set; } 
} 

我也有IBar接口,可以在运行时设置的多种具体实现。其中一些具体的类需要一个定制的JsonConverter来进行序列化&反序列化。

利用TypeNameHandling.Auto选项,需要IBar类的非转换器可以完美地序列化和反序列化。另一方面,自定义序列化的类没有$type名称输出,并且在按预期序列化它们时,它们不能被反序列化为它们的具体类型。

我试图在自定义JsonConverter中写下$type名称元数据;然而,在反序列化转换器然后被完全绕过。

有没有解决方法或适当的方式来处理这种情况?

回答

4

我解决了类似的问题,我找到了解决方案。这不是很优雅,我认为应该有更好的方法,但至少它是有效的。所以我的想法是每个类型都有JsonConverter实现IBar和一个转换器IBar本身。

所以,让我们从模型开始:

public interface IBar { } 

public class BarA : IBar { } 

public class Foo 
{ 
    public IBar Bar { get; set; } 
} 

现在让我们创建转换器IBar。它仅在反序列化JSON时使用。它会尝试读取$type变量,并调用转换器实现类型:

public class BarConverter : JsonConverter 
{ 
    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) 
    { 
     throw new NotSupportedException(); 
    } 

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) 
    { 
     var jObj = JObject.Load(reader); 
     var type = jObj.Value<string>("$type"); 

     if (type == GetTypeString<BarA>()) 
     { 
      return new BarAJsonConverter().ReadJson(reader, objectType, jObj, serializer); 
     } 
     // Other implementations if IBar 

     throw new NotSupportedException(); 
    } 

    public override bool CanConvert(Type objectType) 
    { 
     return objectType == typeof (IBar); 
    } 

    public override bool CanWrite 
    { 
     get { return false; } 
    } 

    private string GetTypeString<T>() 
    { 
     var typeOfT = typeof (T); 
     return string.Format("{0}, {1}", typeOfT.FullName, typeOfT.Assembly.GetName().Name); 
    } 
} 

这是转换器BarA类:

public class BarAJsonConverter : BarBaseJsonConverter 
{ 
    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) 
    { 
     // '$type' property will be added because used serializer has TypeNameHandling = TypeNameHandling.Objects 
     GetSerializer().Serialize(writer, value); 
    } 

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) 
    { 
     var existingJObj = existingValue as JObject; 
     if (existingJObj != null) 
     { 
      return existingJObj.ToObject<BarA>(GetSerializer()); 
     } 

     throw new NotImplementedException(); 
    } 

    public override bool CanConvert(Type objectType) 
    { 
     return objectType == typeof(BarA); 
    } 
} 

你可能会注意到它是从BarBaseJsonConverter类,而不是JsonConverter继承。并且我们也不使用WriteJsonReadJson方法中的serializer参数。在自定义转换器中使用serializer参数时出现问题。您可以阅读更多here。我们需要建立的JsonSerializer新实例和基础类是一个很好的候选人:

public abstract class BarBaseJsonConverter : JsonConverter 
{ 
    public JsonSerializer GetSerializer() 
    { 
     var serializerSettings = JsonHelper.DefaultSerializerSettings; 
     serializerSettings.TypeNameHandling = TypeNameHandling.Objects; 

     var converters = serializerSettings.Converters != null 
      ? serializerSettings.Converters.ToList() 
      : new List<JsonConverter>(); 
     var thisConverter = converters.FirstOrDefault(x => x.GetType() == GetType()); 
     if (thisConverter != null) 
     { 
      converters.Remove(thisConverter); 
     } 
     serializerSettings.Converters = converters; 

     return JsonSerializer.Create(serializerSettings); 
    } 
} 

JsonHelper仅仅是一个类来创建JsonSerializerSettings

public static class JsonHelper 
{ 
    public static JsonSerializerSettings DefaultSerializerSettings 
    { 
     get 
     { 
      return new JsonSerializerSettings 
      { 
       Converters = new JsonConverter[] { new BarConverter(), new BarAJsonConverter() } 
      }; 
     } 
    } 
} 

现在它将会奏效,你还是可以使用两个序列化和反序列化的自定义转换器:

var obj = new Foo { Bar = new BarA() }; 
var json = JsonConvert.SerializeObject(obj, JsonHelper.DefaultSerializerSettings); 
var dObj = JsonConvert.DeserializeObject<Foo>(json, JsonHelper.DefaultSerializerSettings); 
+0

谢谢你为一个伟大的答案!我昨天晚上在类似的事情上工作,但你给了我额外的信息,我需要让它工作。我采取了一种稍微不同的方法,但它是完全通用的,并且不需要所有继承类型的转换器。请参阅下面的自我回答。我会将它标记为答案,因为它让我走上了正确的轨道。 –

+1

我很高兴这有助于解决您的问题。 –

+0

如果正在转换的类型也具有需要转换器的其他类型的引用,则此方法不起作用。您实质上是在禁用转换器的情况下反序列化整个子树。 – torvin

0

从上面Alesandr伊万诺夫的答案使用的信息,我创建了一个通用WrappedJsonConverter<T>类包装(和展开)需要转换器的具体类,使用的$wrappedType元数据属性遵循与标准$type相同的类型名称序列化。

WrappedJsonConverter<T>加入作为转换器的接口(即IBar),但除此之外,此包装是完全透明的,不需要一个转换器类,也无需更改包裹转换器。

我用一个稍微不同的黑客绕过转换器/串行循环(静态字段),但它并不需要使用串行设置的任何知识,并允许IBar对象图有孩子IBar性能。

对于包装的对象JSON的样子:

"IBarProperty" : { 
    "$wrappedType" : "Namespace.ConcreteBar, Namespace", 
    "$wrappedValue" : { 
     "ConvertedID" : 90, 
     "ConvertedPropID" : 70 
     ... 
    } 
} 

The full gist can be found here.

public class WrappedJsonConverter<T> : JsonConverter<T> where T : class 
{   
    [ThreadStatic] 
    private static bool _canWrite = true; 
    [ThreadStatic] 
    private static bool _canRead = true; 

    public override bool CanWrite 
    { 
     get 
     { 
      if (_canWrite) 
       return true; 

      _canWrite = true; 
      return false; 
     } 
    } 

    public override bool CanRead 
    { 
     get 
     { 
      if (_canRead) 
       return true; 

      _canRead = true; 
      return false; 
     } 
    } 

    public override T ReadJson(JsonReader reader, T existingValue, JsonSerializer serializer) 
    { 
     var jsonObject = JObject.Load(reader); 
     JToken token; 
     T value; 

     if (!jsonObject.TryGetValue("$wrappedType", out token)) 
     { 
      //The static _canRead is a terrible hack to get around the serialization loop... 
      _canRead = false; 
      value = jsonObject.ToObject<T>(serializer); 
      _canRead = true; 
      return value; 
     } 

     var typeName = jsonObject.GetValue("$wrappedType").Value<string>(); 

     var type = JsonExtensions.GetTypeFromJsonTypeName(typeName, serializer.Binder); 

     var converter = serializer.Converters.FirstOrDefault(c => c.CanConvert(type) && c.CanRead); 

     var wrappedObjectReader = jsonObject.GetValue("$wrappedValue").CreateReader(); 

     wrappedObjectReader.Read(); 

     if (converter == null) 
     { 
      _canRead = false; 
      value = (T)serializer.Deserialize(wrappedObjectReader, type); 
      _canRead = true; 
     } 
     else 
     { 
      value = (T)converter.ReadJson(wrappedObjectReader, type, existingValue, serializer); 
     } 

     return value; 
    } 

    public override void WriteJson(JsonWriter writer, T value, JsonSerializer serializer) 
    { 
     var type = value.GetType(); 
     var converter = serializer.Converters.FirstOrDefault(c => c.CanConvert(type) && c.CanWrite); 

     if (converter == null) 
     { 
      //This is a terrible hack to get around the serialization loop... 
      _canWrite = false; 
      serializer.Serialize(writer, value, type); 
      _canWrite = true; 
      return; 
     } 

     writer.WriteStartObject(); 
     { 
      writer.WritePropertyName("$wrappedType"); 
      writer.WriteValue(type.GetJsonSimpleTypeName()); 
      writer.WritePropertyName("$wrappedValue"); 

      converter.WriteJson(writer, value, serializer); 
     } 
     writer.WriteEndObject(); 
    } 
} 
+1

使用'static bool _canWrite'和'static bool _canRead'不是线程安全的。由于包含[tag:asp.net-web-api]的几个框架在线程间共享转换器,这可能会导致问题。相反,按照[本答案](https://stackoverflow.com/a/30179162/3744182)中的建议,使用'[ThreadStatic]'或[ThreadLocal ''。 – dbc

+0

@dbc良好的通话,这在我的使用场景(桌面)中不是问题,但是是一个有价值的默认设置!谢谢。 –

+0

与Alesandr Ivanov的答案一样的问题:如果正在转换的类型也引用其他需要转换器的类型,它将无法正常工作。您实质上是在禁用转换器的情况下反序列化整个子树。 – torvin

相关问题