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

标签 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); 我收到了下一个错误:

Self referencing loop detected with type 'Configuration'. Path ''.

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

谢谢!

最佳答案

您收到检测到自引用循环 异常的原因是 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] 并引入额外的私有(private)来完全避免使用转换器序列化和反序列化 "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; }
    }
    

关于c# - Json.NET不同的json结构,基于枚举值,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/37896661/

相关文章:

c# - Prism 实际上为开发人员提供了什么?值得吗?

c# - 在 .Net 中锁定 - 是引用锁定还是对象锁定?

asp.net-mvc - Json 结果在浏览器中提示 "Save As"对话框而不是被处理。 ASP.NET MVC

c# - 使用 Moq 对继承层次结构进行单元测试

c# - 带有 orderby 子句的 linq 查询的返回类型不一致

C# 从 XML 中提取数据

c# - 即使 Process.HasExited 为真,Process.WaitForExit 也不会返回

jquery - 使用 json 对象填充下拉菜单

javascript - 修改json对象并更改mock数据

c# - AutoMapper 将源子列表中的对象映射到目标子实体集合中的现有对象