【问题标题】:How to deserialize into IReadOnlyDictionary with C#?如何使用 C# 反序列化为 IReadOnlyDictionary?
【发布时间】:2015-10-03 11:59:44
【问题描述】:

我正在尝试反序列化 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"
    },
  ],
}

包含public IReadOnlyDictionary<string, string> Parameters { get; set; } 以及许多其他内容的 DTO。

我正在使用最新的 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",
    },
  

哪个有效(因为数组已被删除)。

附:我使用了正则表达式替换,但由于最小的 JSON 更改可能导致它失败,所以我放弃了这种方法。

【问题讨论】:

  • 我可以不用自定义反序列化器吗? ;) 对这些不太熟悉。
  • 请附上您正在使用的一些代码。您收到的错误消息表明您正在尝试将“[...]”形式的某些内容反序列化为对象。
  • 您是否考虑过使用dynamic 类型而不是TResponse
  • 您的 JSON 包含一个序列化为键/值对数组的字典。 DataContractJsonSerializer 默认执行此操作。您可以从这里使用DictionaryToArrayConverterstackoverflow.com/questions/27332723/…

标签: c# json json-deserialization


【解决方案1】:

好吧,这花了我一段时间,但我想通了。

所以简短的回答是,如果可能,请使用针对 .NET v4.5+ 的 NewtonSoft.Json 版本。但是,如果您的应用程序打算在 .NET 4.5 及更低版本上运行,则不能使用此功能。

您收到该错误的原因是您的 NewtonSoft.Json 是针对 v4.5 以下的 .NET 框架。这是因为在 .NET v4.5 中引入了IReadonlyDictionaryThis 是 2013 年的博客文章,介绍了 NewtonSoft 5.0 中 .NET v4.5 的这一新功能。

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

对于packages\Newtonsoft.Json.&lt;version&gt;\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.&lt;version&gt;\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

我什至检查了一个针对 .NET 4.5 的非常旧版本的 Newtonsoft.Json (v6.0),它确实支持只读字典。

【讨论】:

    【解决方案2】:

    你可以编写一个自定义的 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,也适用于您想要使用正则表达式的那个。

    【讨论】:

    • 嗨!抱歉,CanConvert 抛出异常,因为 objectType= {Name = "IReadOnlyDictionary2" FullName = "System.Collections.Generic.IReadOnlyDictionary2[[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],[System.String, mscorlib , Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]"} 但是字典是有什么提示吗? :)
    【解决方案3】:

    我认为,您将不得不分两步完成此操作。如果将参数反序列化为对象数组,则可以使用

    IReadOnlyDictionary<K,V> parametersDict =
        parametersAoO.ToDictionary(v => v.Key, v => v.Value);
    

    得到一本这样的字典。

    【讨论】:

      【解决方案4】:

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

      这是ReadOnlyCollection&lt;TKey, TValue&gt; 和派生类型的转换器。它假定所有只读字典都有一个接受IDictionary&lt;TKey, TValue&gt; 或类似的构造函数。

      它只是将 JSON 反序列化为普通的 Dictionary&lt;TKey, TValue&gt;,然后以该字典作为参数构造 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);
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2019-02-02
        • 1970-01-01
        • 1970-01-01
        • 2018-10-11
        • 2020-07-19
        • 2022-06-14
        • 1970-01-01
        相关资源
        最近更新 更多