2015-04-22 75 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

相關問題