我一直無法找到JsonConvert.WriteJson
的合理實現,它允許我在序列化特定類型時插入JSON屬性。我所有的嘗試都會導致「JsonSerializationException:使用類型XXX檢測到的自引用循環」。Json.NET,如何定製序列化以插入JSON屬性
我想解決的問題多一點背景:我使用JSON作爲配置文件格式,並且我使用JsonConverter
來控制我的配置類型的類型解析,序列化和反序列化。而不是使用$type
屬性,我想使用更有意義的JSON值來解析正確的類型。
在我的削減的例子,這裏的一些JSON文本:
{
"Target": "B",
"Id": "foo"
}
在JSON屬性"Target": "B"
被用來確定這個對象應該被序列化爲B
類型。考慮到這個簡單的例子,這種設計可能看起來並不那麼引人注目,但它確實使配置文件格式更加有用。
我也希望配置文件是可以四捨五入的。我有反序列化的情況下工作,我無法工作的是序列化的情況。
我的問題的根源是,我找不到使用標準JSON序列化邏輯的JsonConverter.WriteJson
的實現,並且不會拋出「自引用循環」異常。下面是我實現的:
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
JProperty typeHintProperty = TypeHintPropertyForType(value.GetType());
//BUG: JsonSerializationException : Self referencing loop detected with type 'B'. Path ''.
// Same error occurs whether I use the serializer parameter or a separate serializer.
JObject jo = JObject.FromObject(value, serializer);
if (typeHintProperty != null)
{
jo.AddFirst(typeHintProperty);
}
writer.WriteToken(jo.CreateReader());
}
在我看來,是在Json.NET一個錯誤,因爲應該有辦法做到這一點。不幸的是,我遇到的所有JsonConverter.WriteJson
的示例(例如Custom conversion of specific objects in JSON.NET)僅使用JsonWriter方法寫出單個對象和屬性,從而提供了特定類的自定義序列化。
下面是一個xUnit的測試的完整代碼顯示出我的問題(或see it here)
using System;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json.Serialization;
using Xunit;
public class A
{
public string Id { get; set; }
public A Child { get; set; }
}
public class B : A {}
public class C : A {}
/// <summary>
/// Shows the problem I'm having serializing classes with Json.
/// </summary>
public sealed class JsonTypeConverterProblem
{
[Fact]
public void ShowSerializationBug()
{
A a = new B()
{
Id = "foo",
Child = new C() { Id = "bar" }
};
JsonSerializerSettings jsonSettings = new JsonSerializerSettings();
jsonSettings.ContractResolver = new TypeHintContractResolver();
string json = JsonConvert.SerializeObject(a, Formatting.Indented, jsonSettings);
Console.WriteLine(json);
Assert.Contains(@"""Target"": ""B""", json);
Assert.Contains(@"""Is"": ""C""", json);
}
[Fact]
public void DeserializationWorks()
{
string json =
@"{
""Target"": ""B"",
""Id"": ""foo"",
""Child"": {
""Is"": ""C"",
""Id"": ""bar"",
}
}";
JsonSerializerSettings jsonSettings = new JsonSerializerSettings();
jsonSettings.ContractResolver = new TypeHintContractResolver();
A a = JsonConvert.DeserializeObject<A>(json, jsonSettings);
Assert.IsType<B>(a);
Assert.IsType<C>(a.Child);
}
}
public class TypeHintContractResolver : DefaultContractResolver
{
public override JsonContract ResolveContract(Type type)
{
JsonContract contract = base.ResolveContract(type);
if ((contract is JsonObjectContract)
&& ((type == typeof(A)) || (type == typeof(B)))) // In the real implementation, this is checking against a registry of types
{
contract.Converter = new TypeHintJsonConverter(type);
}
return contract;
}
}
public class TypeHintJsonConverter : JsonConverter
{
private readonly Type _declaredType;
public TypeHintJsonConverter(Type declaredType)
{
_declaredType = declaredType;
}
public override bool CanConvert(Type objectType)
{
return objectType == _declaredType;
}
// The real implementation of the next 2 methods uses reflection on concrete types to determine the declaredType hint.
// TypeFromTypeHint and TypeHintPropertyForType are the inverse of each other.
private Type TypeFromTypeHint(JObject jo)
{
if (new JValue("B").Equals(jo["Target"]))
{
return typeof(B);
}
else if (new JValue("A").Equals(jo["Hint"]))
{
return typeof(A);
}
else if (new JValue("C").Equals(jo["Is"]))
{
return typeof(C);
}
else
{
throw new ArgumentException("Type not recognized from JSON");
}
}
private JProperty TypeHintPropertyForType(Type type)
{
if (type == typeof(A))
{
return new JProperty("Hint", "A");
}
else if (type == typeof(B))
{
return new JProperty("Target", "B");
}
else if (type == typeof(C))
{
return new JProperty("Is", "C");
}
else
{
return null;
}
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (! CanConvert(objectType))
{
throw new InvalidOperationException("Can't convert declaredType " + objectType + "; expected " + _declaredType);
}
// Load JObject from stream. Turns out we're also called for null arrays of our objects,
// so handle a null by returning one.
var jToken = JToken.Load(reader);
if (jToken.Type == JTokenType.Null)
return null;
if (jToken.Type != JTokenType.Object)
{
throw new InvalidOperationException("Json: expected " + _declaredType + "; got " + jToken.Type);
}
JObject jObject = (JObject) jToken;
// Select the declaredType based on TypeHint
Type deserializingType = TypeFromTypeHint(jObject);
var target = Activator.CreateInstance(deserializingType);
serializer.Populate(jObject.CreateReader(), target);
return target;
}
public override bool CanWrite
{
get { return true; }
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
JProperty typeHintProperty = TypeHintPropertyForType(value.GetType());
//BUG: JsonSerializationException : Self referencing loop detected with type 'B'. Path ''.
// Same error occurs whether I use the serializer parameter or a separate serializer.
JObject jo = JObject.FromObject(value, serializer);
if (typeHintProperty != null)
{
jo.AddFirst(typeHintProperty);
}
writer.WriteToken(jo.CreateReader());
}
}
在你的轉換器的WriteJson方法中, 'JObject的'serializer'參數。FromObject()'調用完全?似乎在[這個小提琴](https://dotnetfiddle.net/lZfCWJ) – 2014-09-30 22:06:05
工作謝謝布賴恩 - 謝謝你看這個,你是對的,修復了異常。它不,但是解決我的問題,因爲我需要能夠在嵌套對象中執行此操作。我已經更新了這個例子。或者,請參閱https://dotnetfiddle.net/b3yrEU(Fiddle is COOL !!) – crimbo 2014-10-01 06:49:58
我會很有興趣瞭解最終結果。我有同樣的問題。 – 2015-07-14 05:33:19