Newtonsoft.Json泛型自定义转换器嵌套对象序列化问题求助
问题描述
我遇到的问题和一篇Stack Overflow帖子完全一致:帖子里的自定义转换器对int、bool、string等简单类型属性有效,但处理嵌套对象时会抛出Newtonsoft.Json.JsonSerializationException: Self referecing loop detected for property 'Value' with type ...异常(比如处理soBillingContact这类对象时)。原方案把对象作为根对象时能正常工作,但作为嵌套对象就会出错。
需求
需要实现一个通用泛型转换器,能同时处理以下两种JSON格式,避免为每个JSON对象单独编写自定义转换器:
格式一(带value嵌套)
{ "printNoteOnInternalDocuments": { "value": true }, "soBillingContact": { "value": { "overrideContact": { "value": true }, "name": { "value": "string" }, "attention": { "value": "string" }, "email": { "value": "string" }, "web": { "value": "string" }, "phone1": { "value": "string" }, "phone2": { "value": "string" }, "fax": { "value": "string" } } } }
格式二(直接值)
{ "printNoteOnInternalDocuments": true, "soBillingContact": { "overrideContact": true, "contactId": 0, "name": "string", "attention": "string", "email": "string", "web": "string", "phone1": "string", "phone2": "string", "fax": "string" } }
目前我已经实现了针对特定对象的自定义转换器(代码如下),但希望改成通用泛型版本。
现有可行代码
Main方法
static void Main(string[] args) { var vSalesOrder = new SalesOrder() { Project = 1, PrintDescriptionOnInvoice = true, PrintNoteOnExternalDocuments = true, SoBillingContact = new Contact { Attention = "attention", Email = "@whatever.se", Fax = "lolfax" } }; var jsonString = JsonConvert.SerializeObject(vSalesOrder); }
SalesOrder类
public class SalesOrder { [JsonProperty("project", NullValueHandling = NullValueHandling.Ignore)] [JsonConverter(typeof(WrapWithValueConverter<int?>))] public int? Project { get; set; } [JsonProperty("printDescriptionOnInvoice", NullValueHandling = NullValueHandling.Ignore)] [JsonConverter(typeof(WrapWithValueConverter<bool>))] public bool PrintDescriptionOnInvoice { get; set; } [JsonProperty("printNoteOnExternalDocuments", NullValueHandling = NullValueHandling.Ignore)] [JsonConverter(typeof(WrapWithValueConverter<bool>))] public bool PrintNoteOnExternalDocuments { get; set; } [JsonProperty("printNoteOnInternalDocuments", NullValueHandling = NullValueHandling.Ignore)] [JsonConverter(typeof(WrapWithValueConverter<bool>))] public bool PrintNoteOnInternalDocuments { get; set; } [JsonProperty("soBillingContact", NullValueHandling = NullValueHandling.Ignore)] [JsonConverter(typeof(ContactDtoJsonConverter))] public Contact SoBillingContact { get; set; } }
ContactDtoJsonConverter类
public class ContactDtoJsonConverter : JsonConverter<Contact> { public override bool CanRead => false; public override bool CanWrite => true; public override Contact ReadJson(JsonReader reader, Type objectType, Contact existingValue, bool hasExistingValue, JsonSerializer serializer) { throw new NotImplementedException(); } public override void WriteJson(JsonWriter writer, Contact value, JsonSerializer serializer) { var dtoContact = new DtoContact { Value = value }; JToken t = JToken.FromObject(dtoContact); JObject o = (JObject)t; o.WriteTo(writer); } }
DtoContact类
public class DtoContact { [JsonProperty("value", NullValueHandling = NullValueHandling.Ignore)] public Contact Value { get; set; } }
Contact类
public class Contact { [JsonProperty("overrideContact", NullValueHandling = NullValueHandling.Ignore)] public bool OverrideContact { get;set; } [JsonProperty("attention", NullValueHandling = NullValueHandling.Ignore)] [JsonConverter(typeof(StringDtoJsonConverter))] public string Attention { get; set; } [JsonProperty("email", NullValueHandling = NullValueHandling.Ignore)] [JsonConverter(typeof(StringDtoJsonConverter))] public string Email { get; set; } [JsonProperty("fax", NullValueHandling = NullValueHandling.Ignore)] [JsonConverter(typeof(StringDtoJsonConverter))] public string Fax { get; set; } [JsonProperty("name", NullValueHandling = NullValueHandling.Ignore)] [JsonConverter(typeof(StringDtoJsonConverter))] public string Name { get; set; } [JsonProperty("phone1", NullValueHandling = NullValueHandling.Ignore)] [JsonConverter(typeof(StringDtoJsonConverter))] public string Phone1 { get; set; } [JsonProperty("phone2", NullValueHandling = NullValueHandling.Ignore)] [JsonConverter(typeof(StringDtoJsonConverter))] public string Phone2 { get; set; } [JsonProperty("web", NullValueHandling = NullValueHandling.Ignore)] [JsonConverter(typeof(StringDtoJsonConverter))] public string Web { get; set; } }
StringDtoJsonConverter类
public class StringDtoJsonConverter : JsonConverter<string> { public override string ReadJson(JsonReader reader, Type objectType, string existingValue, bool hasExistingValue, JsonSerializer serializer) { return (string)reader.Value; } public override void WriteJson(JsonWriter writer, string value, JsonSerializer serializer) { JToken t = JToken.FromObject(value); if (t.Type != JTokenType.Object) { var dtoValue = new DtoString { Value = value }; serializer.Serialize(writer, dtoValue); } } }
解决方案:通用泛型转换器
要解决嵌套对象的自引用循环问题,核心是在序列化时避免重复应用转换器。以下是通用WrapWithValueConverter<T>的实现:
通用泛型转换器代码
public class WrapWithValueConverter<T> : JsonConverter<T> { public override bool CanRead => true; public override bool CanWrite => true; public override T ReadJson(JsonReader reader, Type objectType, T existingValue, bool hasExistingValue, JsonSerializer serializer) { // 兼容两种格式:直接值或带value嵌套的对象 if (reader.TokenType == JsonToken.StartObject) { var jObj = JObject.Load(reader); if (jObj.ContainsKey("value")) { return jObj["value"].ToObject<T>(serializer); } return jObj.ToObject<T>(serializer); } return (T)reader.Value; } public override void WriteJson(JsonWriter writer, T value, JsonSerializer serializer) { // 创建临时序列化器,移除当前转换器避免循环引用 var tempSerializer = new JsonSerializer(); foreach (var converter in serializer.Converters.Where(c => c.GetType() != typeof(WrapWithValueConverter<T>)).ToList()) { tempSerializer.Converters.Add(converter); } tempSerializer.NullValueHandling = serializer.NullValueHandling; // 将值包装为带value的对象 var wrapper = new { value = value }; JToken token = JToken.FromObject(wrapper, tempSerializer); token.WriteTo(writer); } }
使用方式
在所有需要包装的属性(包括嵌套对象)上直接应用该泛型转换器即可:
public class SalesOrder { [JsonProperty("project", NullValueHandling = NullValueHandling.Ignore)] [JsonConverter(typeof(WrapWithValueConverter<int?>))] public int? Project { get; set; } [JsonProperty("printDescriptionOnInvoice", NullValueHandling = NullValueHandling.Ignore)] [JsonConverter(typeof(WrapWithValueConverter<bool>))] public bool PrintDescriptionOnInvoice { get; set; } [JsonProperty("printNoteOnExternalDocuments", NullValueHandling = NullValueHandling.Ignore)] [JsonConverter(typeof(WrapWithValueConverter<bool>))] public bool PrintNoteOnExternalDocuments { get; set; } [JsonProperty("printNoteOnInternalDocuments", NullValueHandling = NullValueHandling.Ignore)] [JsonConverter(typeof(WrapWithValueConverter<bool>))] public bool PrintNoteOnInternalDocuments { get; set; } [JsonProperty("soBillingContact", NullValueHandling = NullValueHandling.Ignore)] [JsonConverter(typeof(WrapWithValueConverter<Contact>))] public Contact SoBillingContact { get; set; } } public class Contact { [JsonProperty("overrideContact", NullValueHandling = NullValueHandling.Ignore)] [JsonConverter(typeof(WrapWithValueConverter<bool>))] public bool OverrideContact { get; set; } [JsonProperty("attention", NullValueHandling = NullValueHandling.Ignore)] [JsonConverter(typeof(WrapWithValueConverter<string>))] public string Attention { get; set; } // 其他字符串属性同理替换为WrapWithValueConverter<string> }
原理说明
- 读取逻辑:判断JSON节点类型,若为对象则检查是否包含
value字段,有则解析value内容,否则直接解析整个对象;若为直接值则直接转换。 - 写入逻辑:创建临时序列化器并移除当前转换器,避免序列化嵌套对象时重复触发转换器导致自引用循环,最后将值包装到带
value的匿名对象中完成序列化。
内容的提问来源于stack exchange,提问作者Snuffsis
相关产品推荐
相关产品推荐

