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

时间:2016-06-18 11:58:11

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

我需要将我的类转换为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);我收到了下一个错误:

  

使用类型&#39;配置&#39;检测到自引用循环。路径&#39;。

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

谢谢!

2 个答案:

答案 0 :(得分:2)

您获得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; }
    }
    

答案 1 :(得分:1)

如果您需要移除该错误,可以配置序列化以忽略参考循环。这是通过使用其中一个SerializaObject()重载来完成的。

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