【问题标题】:Json.NET different json structure, based on enum valueJson.NET 不同的 json 结构,基于枚举值
【发布时间】:2016-10-20 04:22:59
【问题描述】:

我需要将我的类转换为 JSON,并且我使用 Json.NET。但我可以有不同的 JSON 结构,例如:

{
    name: "Name",
    type: "simple1",
    value: 100
};

{
    name: "Name",
    type: {
        optional1: {
            setting1: "s1",
            setting2: "s2",
            ///etc.
    },
    value: 100
};

我的 C# 代码是:

public class Configuration
{
    [JsonProperty(PropertyName = "name")]
    public string Name{ get; set; }

    [JsonProperty(PropertyName = "type")]
    public MyEnumTypes Type { get; set; }

    public OptionalType TypeAdditionalData { get; set; }

    [JsonProperty(PropertyName = "value")]
    public int Value { get; set; }
    public bool ShouldSerializeType()
    {
        OptionalSettingsAttribute optionalSettingsAttr = this.Type.GetAttributeOfType<OptionalSettingsAttribute>();
        return optionalSettingsAttr == null;
    }

    public bool ShouldSerializeTypeAdditionalData()
    {
        OptionalSettingsAttribute optionalSettingsAttr = this.Type.GetAttributeOfType<OptionalSettingsAttribute>();
        return optionalSettingsAttr != null;
    }
}

public enum MyEnumTypes 
{
    [EnumMember(Value = "simple1")]
    Simple1,

    [EnumMember(Value = "simple2")]
    Simple2,

    [OptionalSettingsAttribute]
    [EnumMember(Value = "optional1")]
    Optional1,

    [EnumMember(Value = "optional2")]
    [OptionalSettingsAttribute]
    Optional2
}

我的想法是当Configuration.Type - 值没有属性OptionalSettingsAttribute - 将其序列化为type: "simple1"。否则 - 使用 Configuration.Type - 值作为类型的值键 (type: { optional1: {} }) 和 Configuration.TypeAdditionalData 中的值作为 optional1 - 值(如上面的 2 个简单 JSON)。

我尝试创建一个自定义转换器,例如:

public class ConfigurationCustomConverter : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return typeof(Configuration).IsAssignableFrom(objectType);
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        return serializer.Deserialize<Configuration>(reader);
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        //my changes here

        serializer.Serialize(writer, value);
    }

但是当我将[JsonConverter(typeof(ConfigurationCustomConverter))] 属性添加到Configuration 类时:

[JsonConverter(typeof(ConfigurationCustomConverter))]
public class Configuration

并调用JsonConvert.SerializeObject(configurationObj); 我收到下一个错误:

检测到“配置”类型的自引用循环。路径''。

您知道如何更改我的代码以将我的类序列化为 2 个不同的 JSON 结构吗? 注意:我不会使用同一个类来反序列化 JSON。

谢谢!

【问题讨论】:

    标签: c# .net json serialization json.net


    【解决方案1】:

    您收到Self referencing loop detected 异常的原因是您的转换器的WriteJson 方法正在递归调用自身。当您使用 [JsonConverter(typeof(ConfigurationCustomConverter))] 将转换器应用于类型时,WriteJson() 方法将无条件替换 Json.NET 的默认实现。因此你的内心呼唤:

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        //my changes here
        serializer.Serialize(writer, value);
    }
    

    会导致堆栈溢出。 Json.NET 注意到了这一点,而是抛出了你看到的异常。有关详细信息,请参阅JSON.Net throws StackOverflowException when using [JsonConvert()]。设置ReferenceLoopHandling.Ignore 只会导致无限递归被跳过,让你的对象为空。

    你有几个选项来解决这个问题:

    1. 您可以手动编写除TypeTypeAdditionalData 之外的所有属性名称和值,然后最后写出自定义"type" 属性。例如:

      [JsonConverter(typeof(ConfigurationConverter))]
      public class Configuration
      {
          [JsonProperty(PropertyName = "name")]
          public string Name { get; set; }
      
          public MyEnumTypes Type { get; set; }
      
          public OptionalType TypeAdditionalData { get; set; }
      
          [JsonProperty(PropertyName = "value")]
          public int Value { get; set; }
      }
      
      class ConfigurationConverter : JsonConverter
      {
          const string typeName = "type";
      
          public override bool CanConvert(Type objectType)
          {
              return typeof(Configuration).IsAssignableFrom(objectType);
          }
      
          public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
          {
              if (reader.TokenType == JsonToken.Null)
                  return null;
              var config = (existingValue as Configuration ?? (Configuration)serializer.ContractResolver.ResolveContract(objectType).DefaultCreator());
      
              // Populate the regular property values.
              var obj = JObject.Load(reader);
              var type = obj.RemoveProperty(typeName);
              using (var subReader = obj.CreateReader())
                  serializer.Populate(subReader, config);
      
              // Populate Type and OptionalType
              if (type is JValue) // Primitive value
              {
                  config.Type = type.ToObject<MyEnumTypes>(serializer);
              }
              else
              {
                  var dictionary = type.ToObject<Dictionary<MyEnumTypes, OptionalType>>(serializer);
                  if (dictionary.Count > 0)
                  {
                      config.Type = dictionary.Keys.First();
                      config.TypeAdditionalData = dictionary.Values.First();
                  }
              }
      
              return config;
          }
      
          public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
          {
              var config = (Configuration)value;
              var contract = (JsonObjectContract)serializer.ContractResolver.ResolveContract(config.GetType());
              writer.WriteStartObject();
              foreach (var property in contract.Properties
                  .Where(p => p.Writable && (p.ShouldSerialize == null || p.ShouldSerialize(config)) && !p.Ignored))
              {
                  if (property.UnderlyingName == "Type" || property.UnderlyingName == "TypeAdditionalData")
                      continue;
                  var propertyValue = property.ValueProvider.GetValue(config);
                  if (propertyValue == null && serializer.NullValueHandling == NullValueHandling.Ignore)
                      continue;
                  writer.WritePropertyName(property.PropertyName);
                  serializer.Serialize(writer, propertyValue);
              }
              writer.WritePropertyName(typeName);
              if (config.Type.GetCustomAttributeOfEnum<OptionalSettingsAttribute>() == null)
              {
                  serializer.Serialize(writer, config.Type);
              }
              else
              {
                  var dictionary = new Dictionary<MyEnumTypes, OptionalType>
                  {
                      { config.Type, config.TypeAdditionalData },
                  };
                  serializer.Serialize(writer, dictionary);
              }
              writer.WriteEndObject();
          }
      }
      
      public class OptionalType
      {
          public string setting1 { get; set; }
      }
      
      public class OptionalSettingsAttribute : System.Attribute
      {
          public OptionalSettingsAttribute()
          {
          }
      }
      
      [JsonConverter(typeof(StringEnumConverter))]
      public enum MyEnumTypes
      {
          [EnumMember(Value = "simple1")]
          Simple1,
      
          [EnumMember(Value = "simple2")]
          Simple2,
      
          [OptionalSettingsAttribute]
          [EnumMember(Value = "optional1")]
          Optional1,
      
          [EnumMember(Value = "optional2")]
          [OptionalSettingsAttribute]
          Optional2
      }
      
      public static class EnumExtensions
      {
          public static TAttribute GetCustomAttributeOfEnum<TAttribute>(this Enum value)
              where TAttribute : System.Attribute
          {
              var type = value.GetType();
              var memInfo = type.GetMember(value.ToString());
              return memInfo[0].GetCustomAttribute<TAttribute>();
          }
      }
      
      public static class JsonExtensions
      {
          public static JToken RemoveProperty(this JObject obj, string name)
          {
              if (obj == null)
                  return null;
              var property = obj.Property(name);
              if (property == null)
                  return null;
              var value = property.Value;
              property.Remove();
              property.Value = null;
              return value;
          }
      }
      

      请注意,我已将 [JsonConverter(typeof(StringEnumConverter))] 添加到您的枚举中。这样可以确保类型始终写为字符串。

      示例fiddle

    2. 您可以通过JSON.Net throws StackOverflowException when using [JsonConvert()] 中显示的技术禁用对转换器的递归调用,生成默认序列化,根据需要对其进行修改,然后将其写出。

    3. 您可以通过将TypeTypeAdditionalData 标记为[JsonIgnore] 并引入额外的私有属性来序列化和反序列化"type" 来完全避免使用转换器:

      public class Configuration
      {
          [JsonProperty(PropertyName = "name")]
          public string Name { get; set; }
      
          [JsonIgnore]
          public MyEnumTypes Type { get; set; }
      
          [JsonIgnore]
          public OptionalType TypeAdditionalData { get; set; }
      
          [JsonProperty("type")]
          JToken SerializedType
          {
              get
              {
                  if (Type.GetCustomAttributeOfEnum<OptionalSettingsAttribute>() == null)
                  {
                      return JToken.FromObject(Type);
                  }
                  else
                  {
                      var dictionary = new Dictionary<MyEnumTypes, OptionalType>
                      {
                          { Type, TypeAdditionalData },
                      };
                      return JToken.FromObject(dictionary);
                  }
              }
              set
              {
                  if (value == null || value.Type == JTokenType.Null)
                  {
                      TypeAdditionalData = null;
                      Type = default(MyEnumTypes);
                  }
                  else if (value is JValue)
                  {
                      Type = value.ToObject<MyEnumTypes>();
                  }
                  else
                  {
                      var dictionary = value.ToObject<Dictionary<MyEnumTypes, OptionalType>>();
                      if (dictionary.Count > 0)
                      {
                          Type = dictionary.Keys.First();
                          TypeAdditionalData = dictionary.Values.First();
                      }
                  }
              }
          }
      
          [JsonProperty(PropertyName = "value")]
          public int Value { get; set; }
      }
      

    【讨论】:

    • 非常感谢@dbc!我尝试了解决方案#3,它对我有用!
    【解决方案2】:

    如果您需要跳过该错误,您可以将序列化配置为忽略引用循环。这是通过使用SerializaObject() 重载之一完成的。

    JsonConvert.SerializeObject(configurationObj,
                        new JsonSerializerSettings()
                        { 
                            ReferenceLoopHandling = ReferenceLoopHandling.Ignore
                        });
    

    【讨论】:

    • 谢谢!这修复了“引用循环”错误,但如果 WriteJson 方法有下一个代码:public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { serializer.Serialize(writer, value); } 序列化字符串为空 - 没有任何转换。这是正确的吗?如何在自定义转换器中使用默认转换器?谢谢!
    • 我没有遇到您所描述的情况,我也没有在我的电脑旁边进行更多调查。我将在星期一。希望在那之前有其他人可以提供帮助。请使用我提供的信息更新您的问题,以便下一个人可以帮助回答完整的问题。
    • 谢谢!我在@dbc 中使用了解决方案#3 - 回答,它对我有用!
    猜你喜欢
    • 2010-10-12
    • 2018-04-18
    • 2019-03-23
    • 2014-09-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多