You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.10 08:15:32