如何用c#反序列化成IReadOnlyDictionary

本文关键字:IReadOnlyDictionary 反序列化 何用 | 更新日期: 2023-09-27 18:02:35

我试图反序列化JSON

{
  "Type": "Correction",
  "StartTime": "2007-12-19T03:00:00.0000000-08:00",
  "EndTime": "2007-12-23T23:00:00.0000000-08:00",
  "Parameters": [
    {
      "Key": "Something",
      "Value": "1.8"
    },
    {
      "Key": "Something2",
      "Value": "0.10000000000000001"
    },
    {
      "Key": "Something3",
      "Value": "answer3"
    },
  ],
}

变成一个DTO,包括public IReadOnlyDictionary<string, string> Parameters { get; set; }和许多其他东西。

我正在使用最新的Newtonsoft反序列化器,函数为

var responseObject = JsonConvert.DeserializeObject<TResponse>(jsonResponse);

但是它返回错误

Newtonsoft.Json.JsonSerializationException : Cannot deserialize the current JSON array (e.g. [1,2,3]) into type 'System.Collections.Generic.IReadOnlyDictionary`2[System.String,System.String]' because the type requires a JSON object (e.g. {"name":"value"}) to deserialize correctly.

是否有工具可以帮助我将JSON响应更改为不同的响应,例如

"Parameters": 
    {
      "Something": "1.8",
      "Something2": "0.10000000000000001",
      "Something3": "answer3",
    },
  

可以工作(因为数组被删除了)。

注:我已经使用了regex替换,但由于最小的JSON更改可能导致它失败,所以我已经放弃了这种方法。

如何用c#反序列化成IReadOnlyDictionary

好吧,这花了我一段时间,但我明白了。

所以简短的答案是,使用NewtonSoft版本。如果可能的话,以。net v4.5+为目标的Json。但是,如果你的应用程序打算在。net 4.5及以下版本上运行,你就不能使用这个特性。

你得到这个错误的原因是因为你的NewtonSoft。Json的目标是v4.5以下的。net框架。这是因为IReadonlyDictionary是在。net v4.5中引入的。这是2013年的一篇博文,介绍了NewtonSoft 5.0中。net v4.5的新特性。

newtonsoft.json nuget包中,有多个版本的程序集针对不同的。net版本。我使用ildasm来窥视程序集元数据。

对于packages'Newtonsoft.Json.<version>'lib'net40'Newtonsoft.Json.dll,它的TargetFramework设置为v4.0,并且它的实现支持反序列化到IReadonlyDictionary:

.custom instance void [mscorlib]System.Runtime.Versioning.TargetFrameworkAttribute::.ctor(string) = ( 01 00 1A 2E 4E 45 54 46 72 61 6D 65 77 6F 72 6B // ....NETFramework 2C 56 65 72 73 69 6F 6E 3D 76 34 2E 30 01 00 54 // ,Version=v4.0..T 0E 14 46 72 61 6D 65 77 6F 72 6B 44 69 73 70 6C // ..FrameworkDispl 61 79 4E 61 6D 65 10 2E 4E 45 54 20 46 72 61 6D // ayName..NET Fram 65 77 6F 72 6B 20 34 ) // ework 4

对于packages'Newtonsoft.Json.<version>'lib'net45'Newtonsoft.Json.dll,它将TargetFramework设置为v4.5,并且它的实现支持反序列化到IReadonlyDictionary

.custom instance void [mscorlib]System.Runtime.Versioning.TargetFrameworkAttribute::.ctor(string) = ( 01 00 1A 2E 4E 45 54 46 72 61 6D 65 77 6F 72 6B // ....NETFramework 2C 56 65 72 73 69 6F 6E 3D 76 34 2E 35 01 00 54 // ,Version=v4.5..T 0E 14 46 72 61 6D 65 77 6F 72 6B 44 69 73 70 6C // ..FrameworkDispl 61 79 4E 61 6D 65 12 2E 4E 45 54 20 46 72 61 6D // ayName..NET Fram 65 77 6F 72 6B 20 34 2E 35 ) // ework 4.5

我甚至检查了一个非常旧的版本Newtonsoft。Json (v6.0),目标是。net 4.5,它支持只读字典。

你可以写一个自定义的JsonConverter

public class KVListToDictConverter<T1,T2> : Newtonsoft.Json.JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return typeof(Dictionary<T1, T2>) == objectType;
    }
    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
            if (reader.TokenType == JsonToken.StartArray)
                return serializer.Deserialize<List<KeyValuePair<T1, T2>>>(reader).ToDictionary(x => x.Key, x => x.Value);
            else
            {
                var c = serializer.Converters.First();
                serializer.Converters.Clear(); //to avoid infinite recursion
                var dict =  serializer.Deserialize<Dictionary<T1, T2>>(reader);
                serializer.Converters.Add(c);
                return dict;
            }
    }
    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }
}

和在反序列化中使用,如

var json = JsonConvert.DeserializeObject<YourObject>(json, new KVListToDictConverter<string,string>());

这既适用于您的第一个json,也适用于您想要使用regex获得的json。

在编写本文时,您应该使用System.Text.Json的自定义转换器。

这是ReadOnlyDictionary<TKey, TValue>和派生类型的转换器。它假设所有只读字典都有一个接受IDictionary<TKey, TValue>或类似类型的构造函数。

它简单地将JSON反序列化为普通的Dictionary<TKey, TValue>,然后用该字典作为参数构造ReadOnlyDictionary类型。

using System.Collections.ObjectModel;
using System.Reflection;
namespace System.Text.Json.Serialization
{
    public class JsonReadOnlyDictionaryConverter : JsonConverterFactory
    {
        public override bool CanConvert(Type typeToConvert)
        {
            if (!typeToConvert.GetInterfaces().Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IReadOnlyDictionary<,>)))
                return false;
            if ((typeToConvert.IsGenericType && typeToConvert.GetGenericTypeDefinition() != typeof(ReadOnlyDictionary<,>)) &&
                !typeof(ReadOnlyDictionary<,>).IsSubclassOfRawGeneric(typeToConvert))
                return false;
            return true;
        }
        public override JsonConverter? CreateConverter(Type typeToConvert, JsonSerializerOptions options)
        {
            var iReadOnlyDictionary = typeToConvert.GetInterfaces().First(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IReadOnlyDictionary<,>));
            Type keyType = iReadOnlyDictionary.GetGenericArguments()[0];
            Type valueType = iReadOnlyDictionary.GetGenericArguments()[1];
            JsonConverter converter = (JsonConverter)Activator.CreateInstance(
                typeof(ReadOnlyDictionaryConverterInner<,>).MakeGenericType(keyType, valueType),
                BindingFlags.Instance | BindingFlags.Public,
                binder: null, args: null, culture: null);
            return converter;
        }
        private class ReadOnlyDictionaryConverterInner<TKey, TValue> : JsonConverter<IReadOnlyDictionary<TKey, TValue>>
            where TKey : notnull
        {
            public override IReadOnlyDictionary<TKey, TValue>? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
            {
                var dictionary = JsonSerializer.Deserialize<Dictionary<TKey, TValue>>(ref reader, options: options);
                if (dictionary == null)
                    return null;
                return (IReadOnlyDictionary<TKey, TValue>)Activator.CreateInstance(
                    typeToConvert, BindingFlags.Instance | BindingFlags.Public,
                    binder: null, args: new object[] { dictionary }, culture: null);
            }
            public override void Write(Utf8JsonWriter writer, IReadOnlyDictionary<TKey, TValue> dictionary, JsonSerializerOptions options) =>
                JsonSerializer.Serialize(writer, dictionary, options);
        }
    }
}

你可以把它变成一个自定义的JsonConverterAttribute,并用它来装饰你的类/属性(我更喜欢):

namespace System.Text.Json.Serialization
{
    public class JsonReadOnlyDictionaryAttribute : JsonConverterAttribute
    {
        public JsonReadOnlyDictionaryAttribute() : base(typeof(JsonReadOnlyDictionaryConverter))
        {
        }
    }
}

或者使用JsonSerializerOptions:

var serializeOptions = new JsonSerializerOptions
{
    Converters =
    {
        new JsonReadOnlyDictionaryConverter()
    }
};
jsonString = JsonSerializer.Serialize(weatherForecast, serializeOptions);

我认为你必须分两步来做这件事。如果将参数反序列化为对象数组,则可以使用

IReadOnlyDictionary<K,V> parametersDict =
    parametersAoO.ToDictionary(v => v.Key, v => v.Value);
相关文章:
  • 没有找到相关文章