Json.NET 不同的 json 结构,基于枚举值

Pep*_*epo 5 .net c# serialization json json.net

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

{
    name: "Name",
    type: "simple1",
    value: 100
};
Run Code Online (Sandbox Code Playgroud)

或者

{
    name: "Name",
    type: {
        optional1: {
            setting1: "s1",
            setting2: "s2",
            ///etc.
    },
    value: 100
};
Run Code Online (Sandbox Code Playgroud)

我的 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
}
Run Code Online (Sandbox Code Playgroud)

我的想法是当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);
    }
Run Code Online (Sandbox Code Playgroud)

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

[JsonConverter(typeof(ConfigurationCustomConverter))]
public class Configuration
Run Code Online (Sandbox Code Playgroud)

并打电话给JsonConvert.SerializeObject(configurationObj);我收到下一个错误:

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

您对如何更改我的代码以将我的类序列化为 2 个不同的 JSON 结构有任何想法吗? 注意:我不会使用相同的类来反序列化 JSON。

谢谢!

dbc*_*dbc 4

The reason you are getting the Self referencing loop detected exception is that the WriteJson method of your converter is calling itself recursively. When you apply a converter to a type using [JsonConverter(typeof(ConfigurationCustomConverter))], the WriteJson() method will unconditionally replace Json.NET's default implementation. Thus your inner call:

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

would cause a stack overflow. Json.NET notices this and instead throws the exception you see. For more details, see JSON.Net throws StackOverflowException when using [JsonConvert()]. Setting ReferenceLoopHandling.Ignore simply causes the infinite recursion to be skipped, leaving your object empty.

You have a few options to solve this problem:

  1. You could manually write all property names and values other than Type and TypeAdditionalData then write out the custom "type" property last. For instance:

    [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;
        }
    }
    
    Run Code Online (Sandbox Code Playgroud)

    Notice I added [JsonConverter(typeof(StringEnumConverter))] to your enum. This ensures the type is always written as a string.

    Sample fiddle.

  2. You could disable recursive calls to the converter via the technique shown in JSON.Net throws StackOverflowException when using [JsonConvert()], generate a default serialization, modify it as required, and write it out.

  3. You could avoid the use of a converter entirely by marking Type and TypeAdditionalData as [JsonIgnore] and introducing an additional private property to serialize and deserialize "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; }
    }
    
    Run Code Online (Sandbox Code Playgroud)