小编典典

使用JSON.Net的字典中复杂类型的特定于用法的序列化

json

我有一堂课

public class MyValue 
{
    public String Prop1 { get; set; }
    public String Prop2 { get; set; }
}

我都将其用作普通Property的类型以及Dictionary键。

我需要的是一种方法,当将该类用作属性时,将其序列化为

{"Prop1":"foo","Prop2":"bar"}

但是当它用作Dictionary键时,它以JSON.Net能够正确反序列化的方式进行序列化。

当将ToString()方法添加到MyValue时,我能够创建文本表示形式(非JSON),该文本表示形式可以用作Dictionary键,但不幸的是,此后我无法对其进行反序列化。甚至为MyValue添加JsonConverter也无济于事,因为它似乎无法将非JSON作为源格式进行处理(此外,当序列化为属性时,它是json,因此转换器需要以某种方式处理两者)。


阅读 187

收藏
2020-07-27

共1个答案

小编典典

您可以做的是在代理KeyValuePair<string, string>数组中序列化和反序列化字典,如下所示:

[DataContract]
public class MyContainer
{
    public MyContainer() {
        this.Dictionary = new Dictionary<MyValue, int>();
    }

    [DataMember]
    public MyValue MyValue { get; set; }

    [IgnoreDataMember]
    public Dictionary<MyValue, int> Dictionary { get; set; }

    [DataMember(Name="Dictionary")]
    private KeyValuePair<MyValue, int> [] SerializedDictionary
    {
        get
        {
            if (Dictionary == null)
                return null;
            return Dictionary.ToArray();
        }
        set
        {
            if (value == null)
            {
                Dictionary = null;
            }
            else
            {
                Dictionary = value.ToDictionary(pair => pair.Key, pair => pair.Value);
            }
        }
    }
}

(在这里,我正在使用DataContract属性,但是我可以很容易地使用[JsonIgnore][JsonProperty("Dictionary")]

因此,要对此进行测试(并假设您已正确覆盖GetHashCode()并且Equals()在上进行了重写MyValue,然后才能将其用作字典键),我做了以下工作:

public static class TestDictionaryJson
{
    public static void Test()
    {
        var dict = new Dictionary<MyValue, int>();
        dict[(new MyValue("A", "A"))] = 1;
        dict[(new MyValue("B", "B"))] = 2;

        var myContainer = new MyContainer() { MyValue = new MyValue("A Property", "At the top level"), Dictionary = dict };

        var json = JsonConvert.SerializeObject(myContainer, Formatting.Indented);

        Debug.WriteLine(json);

        try
        {
            var newContainer = JsonConvert.DeserializeObject<MyContainer>(json);
        }
        catch (Exception ex)
        {
            Debug.Assert(false, ex.ToString()); // No assert - no exception is thrown.
        }

        try
        {
            var dictjson = JsonConvert.SerializeObject(dict, Formatting.Indented);
            Debug.WriteLine(dictjson);
            var newDict = JsonConvert.DeserializeObject<Dictionary<MyValue, int>>(dictjson);
        }
        catch (Exception ex)
        {
            Debug.WriteLine("Caught expected exception deserializing dictionary directly: " + ex.ToString());
        }
    }
}

当然,对容器反序列化也没有例外,但是直接对字典 进行 反序列化。并且为容器创建了以下JSON:

{
  "MyValue": {
    "Prop1": "A Property",
    "Prop2": "At the top level"
  },
  "Dictionary": [
    {
      "Key": {
        "Prop1": "A",
        "Prop2": "A"
      },
      "Value": 1
    },
    {
      "Key": {
        "Prop1": "B",
        "Prop2": "B"
      },
      "Value": 2
    }
  ]
}

那是你要的吗?

更新资料

或者,如果您不喜欢代理数组,则可以将以下内容JsonConverterAttribute应用于每个Dictionary属性,以获得相同的结果:

public class MyContainer
{
    public MyContainer()
    {
        this.Dictionary = new Dictionary<MyValue, int>();
    }

    public MyValue MyValue { get; set; }

    [JsonConverter(typeof(DictionaryToArrayConverter<MyValue, int>))]
    public Dictionary<MyValue, int> Dictionary { get; set; }
}

public class DictionaryToArrayConverter<TKey, TValue> : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return objectType == typeof(Dictionary<TKey, TValue>);
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        KeyValuePair<TKey, TValue>[] pairs;

        JToken token = JToken.Load(reader);
        if (token.Type == JTokenType.Array)
        {
            pairs = token.ToObject<KeyValuePair<TKey, TValue>[]>(serializer);
        }
        else
        {
            JArray array = new JArray();
            array.Add(token);
            pairs = token.ToObject<KeyValuePair<TKey, TValue>[]>(serializer);
        }
        if (pairs == null)
            return null;
        return pairs.ToDictionary(pair => pair.Key, pair => pair.Value);
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        if (value == null)
            return;
        var pairs = ((IDictionary<TKey, TValue>)value).ToArray();
        serializer.Serialize(writer, pairs);
    }
}

更新资料

作为替代方案,您可以密封您的MyValue课程,并附加一个适当的内容,TypeConverterAttribute用于将&转换为字符串。JSON.Net会选择并将其用于字典键和属性。该解决方案比较简单,因为它是一个全局解决方案,因此您不需要为每个字典都使用代理数组或转换器属性,但是为您的MyValue属性创建的JSON
并不是您所需要的。

从而:

public class MyValueConverter : TypeConverter
{
    public override bool CanConvertFrom(ITypeDescriptorContext context,
       Type sourceType)
    {
        if (sourceType == typeof(string))
        {
            return true;
        }
        return base.CanConvertFrom(context, sourceType);
    }

    public override object ConvertFrom(ITypeDescriptorContext context,
       CultureInfo culture, object value)
    {
        if (value is string)
        {
            // Cannot do JsonConvert.DeserializeObject here because it will cause a stackoverflow exception.
            using (var reader = new JsonTextReader(new StringReader((string)value)))
            {
                JObject item = JObject.Load(reader);
                if (item == null)
                    return null;
                MyValue myValue = new MyValue();
                var prop1 = item["Prop1"];
                if (prop1 != null)
                    myValue.Prop1 = prop1.ToString();
                var prop2 = item["Prop2"];
                if (prop2 != null)
                    myValue.Prop2 = prop2.ToString();
                return myValue;
            }
        }
        return base.ConvertFrom(context, culture, value);
    }

    public override object ConvertTo(ITypeDescriptorContext context,
       CultureInfo culture, object value, Type destinationType)
    {
        if (destinationType == typeof(string))
        {
            MyValue myValue = (MyValue)value;

            // Cannot do JsonConvert.SerializeObject here because it will cause a stackoverflow exception.
            StringBuilder sb = new StringBuilder();
            using (StringWriter sw = new StringWriter(sb, CultureInfo.InvariantCulture))
            using (JsonTextWriter jsonWriter = new JsonTextWriter(sw))
            {
                jsonWriter.WriteStartObject();
                jsonWriter.WritePropertyName("Prop1");
                jsonWriter.WriteValue(myValue.Prop1);
                jsonWriter.WritePropertyName("Prop2");
                jsonWriter.WriteValue(myValue.Prop2);
                jsonWriter.WriteEndObject();

                return sw.ToString();
            }
        }
        return base.ConvertTo(context, culture, value, destinationType);
    }
}

[TypeConverter(typeof(MyValueConverter))]
public class MyValue
{
    public MyValue()
    {
    }

    public MyValue(string prop1, string prop2)
    {
        this.Prop1 = prop1;
        this.Prop2 = prop2;
    }

    public String Prop1 { get; set; }
    public String Prop2 { get; set; }

    public override bool Equals(object obj)
    {
        if (ReferenceEquals(this, obj))
            return true;
        else if (ReferenceEquals(obj, null))
            return false;
        if (GetType() != obj.GetType())
            return false;
        var other = (MyValue)obj;
        return Prop1 == other.Prop1 && Prop2 == other.Prop2;
    }

    public override int GetHashCode()
    {
        unchecked
        {
            uint code = 0;
            if (Prop1 != null)
                code ^= (uint)Prop1.GetHashCode();
            code = (code << 16) | (code >> 16);
            if (Prop2 != null)
                code ^= (uint)Prop2.GetHashCode();
            return (int)code;
        }
    }

    public override string ToString()
    {
        return TypeDescriptor.GetConverter(GetType()).ConvertToString(this);
    }

    public static bool operator ==(MyValue first, MyValue second)
    {
        if (ReferenceEquals(first, null))
            return ReferenceEquals(second, null);
        return first.Equals(second);
    }

    public static bool operator !=(MyValue first, MyValue second)
    {
        return !(first == second);
    }
}

现在可以在不使用任何代理数组的情况下序列化使用此类的属性和词典。例如,序列化和反序列化以下内容:

public class MyContainer
{
    public MyContainer()
    {
        this.Dictionary = new Dictionary<MyValue, int>();
    }

    public MyValue MyValue { get; set; }

    public Dictionary<MyValue, int> Dictionary { get; set; }
}

序列化时提供以下JSON:

{
  "MyValue": "{\"Prop1\":\"A Property\",\"Prop2\":\"At the top level\"}",
  "Dictionary": {
    "{\"Prop1\":\"A\",\"Prop2\":\"A\"}": 1,
    "{\"Prop1\":\"B\",\"Prop2\":\"B\"}": 2
  }
}

(引号被转义,因为它们嵌入在JSON中,而不是JSON的一部分。)

后期更新-TypeConverter为字典键创建通用

TypeConverter通过使用适当的合同解析器,可以创建适用于任何通用指定类型的通用:

public class NoTypeConverterContractResolver : DefaultContractResolver
{
    readonly Type type;

    public NoTypeConverterContractResolver(Type type)
        : base()
    {
        if (type == null)
            throw new ArgumentNullException();
        if (type == typeof(string) || type.IsPrimitive)
            throw new ArgumentException("type == typeof(string) || type.IsPrimitive");
        this.type = type;
    }

    protected override JsonContract CreateContract(Type objectType)
    {
        if (type.IsAssignableFrom(objectType))
        {
            // Replaces JsonStringContract for the specified type.
            var contract = this.CreateObjectContract(objectType);
            return contract;
        }
        return base.CreateContract(objectType);
    }
}

public class GenericJsonTypeConverter<T> : TypeConverter
{
    // As of 7.0.1, Json.NET suggests using a static instance for "stateless" contract resolvers, for performance reasons.
    // http://www.newtonsoft.com/json/help/html/ContractResolver.htm
    // http://www.newtonsoft.com/json/help/html/M_Newtonsoft_Json_Serialization_DefaultContractResolver__ctor_1.htm
    // "Use the parameterless constructor and cache instances of the contract resolver within your application for optimal performance."
    static NoTypeConverterContractResolver contractResolver;

    static NoTypeConverterContractResolver ContractResolver
    {
        get
        {
            if (contractResolver == null)
                Interlocked.CompareExchange(ref contractResolver, new NoTypeConverterContractResolver(typeof(T)), null);
            return contractResolver;
        }
    }

    public override bool CanConvertFrom(ITypeDescriptorContext context,
       Type sourceType)
    {
        if (sourceType == typeof(string))
        {
            return true;
        }
        return base.CanConvertFrom(context, sourceType);
    }

    public override object ConvertFrom(ITypeDescriptorContext context,
       CultureInfo culture, object value)
    {
        if (value is string)
        {
            using (var reader = new JsonTextReader(new StringReader((string)value)))
            {
                var obj = JsonSerializer.Create(new JsonSerializerSettings { ContractResolver = ContractResolver }).Deserialize<T>(reader);
                return obj;
            }
        }
        return base.ConvertFrom(context, culture, value);
    }

    public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
    {
        if (destinationType == typeof(string))
        {
            StringBuilder sb = new StringBuilder();
            using (StringWriter sw = new StringWriter(sb, CultureInfo.InvariantCulture))
            using (JsonTextWriter jsonWriter = new JsonTextWriter(sw))
            {
                JsonSerializer.Create(new JsonSerializerSettings { ContractResolver = ContractResolver }).Serialize(jsonWriter, value);
            }
            return sb.ToString();
        }
        return base.ConvertTo(context, culture, value, destinationType);
    }
}

然后将其应用于您的班级,如下所示:

[TypeConverter(typeof(GenericJsonTypeConverter<MyValue>))]
public class MyValue
{

}
2020-07-27