如何使用 C# 反序列化为 IReadOnlyDictionary?
How to deserialize into IReadOnlyDictionary with C#?
我正在尝试反序列化 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);
但是returns错误
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",
},
有效(因为删除了数组)。
P.S。我已经使用正则表达式替换,但由于最小的 JSON 更改可能会导致它失败,我放弃了这种方法。
我认为您必须分两步完成此操作。如果将 Parameters 反序列化为对象数组,则可以使用
IReadOnlyDictionary<K,V> parametersDict =
parametersAoO.ToDictionary(v => v.Key, v => v.Value);
得到这样的字典。
您可以编写自定义 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,也适用于您想使用正则表达式获得的那个。
好的,这花了我一段时间,但我想通了。
所以简短的回答是,尽可能使用面向 .NET v4.5+ 的 NewtonSoft.Json 版本。但是,如果您的应用程序打算 运行 在 .NET 4.5 及更低版本上运行,则您不能使用此功能。
您收到该错误的原因是您的 NewtonSoft.Json 针对的是低于 v4.5 的 .NET 框架。这是因为 .NET v4.5 中引入了 IReadonlyDictionary
。 This 是 2013 年的博客 post,在 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
我什至检查了一个针对 .NET 4.5 的非常旧版本的 Newtonsoft.Json (v6.0),它确实支持只读字典。
在撰写本文时,您应该使用具有 System.Text.Json 的自定义转换器。
这是 ReadOnlyCollection<TKey, TValue>
和派生类型的转换器。它假定所有 read-only 字典都有一个接受 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
并用它装饰你的 class/property(我更喜欢):
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);
我正在尝试反序列化 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);
但是returns错误
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",
},
有效(因为删除了数组)。
P.S。我已经使用正则表达式替换,但由于最小的 JSON 更改可能会导致它失败,我放弃了这种方法。
我认为您必须分两步完成此操作。如果将 Parameters 反序列化为对象数组,则可以使用
IReadOnlyDictionary<K,V> parametersDict =
parametersAoO.ToDictionary(v => v.Key, v => v.Value);
得到这样的字典。
您可以编写自定义 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,也适用于您想使用正则表达式获得的那个。
好的,这花了我一段时间,但我想通了。
所以简短的回答是,尽可能使用面向 .NET v4.5+ 的 NewtonSoft.Json 版本。但是,如果您的应用程序打算 运行 在 .NET 4.5 及更低版本上运行,则您不能使用此功能。
您收到该错误的原因是您的 NewtonSoft.Json 针对的是低于 v4.5 的 .NET 框架。这是因为 .NET v4.5 中引入了 IReadonlyDictionary
。 This 是 2013 年的博客 post,在 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
我什至检查了一个针对 .NET 4.5 的非常旧版本的 Newtonsoft.Json (v6.0),它确实支持只读字典。
在撰写本文时,您应该使用具有 System.Text.Json 的自定义转换器。
这是 ReadOnlyCollection<TKey, TValue>
和派生类型的转换器。它假定所有 read-only 字典都有一个接受 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
并用它装饰你的 class/property(我更喜欢):
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);