将单个 KeyValuePair<> 序列化为 JSON 作为父对象的一部分
Posted
技术标签:
【中文标题】将单个 KeyValuePair<> 序列化为 JSON 作为父对象的一部分【英文标题】:Serialize single KeyValuePair<> to JSON as part of a parent object 【发布时间】:2020-08-31 01:58:44 【问题描述】:我有一个这样的 DTO 类:
public class MyDto
public KeyValuePair<string, string> Identifier get; set;
public double Value1 get; set;
public string Value2 get; set;
两个实例可能如下所示:
var results = new List<MyDto>
new MyDto
Identifier = new KeyValuePair<string, string>("Car", "Ford"),
Value1 = 13,
Value2 = "A"
,
new MyDto
Identifier = new KeyValuePair<string, string>("Train", "Bombardier"),
Value1 = 14,
Value2 = "B"
,
;
在 ASP.NET Web API(使用 Json.NET)中序列化结果时,输出如下所示:
[
"Identifier":
"Key": "Car",
"Value": "Ford"
,
"Value1": 13,
"Value2": "A"
,
"Identifier":
"Key": "Train",
"Value": "Bombardier"
,
"Value1": 14,
"Value2": "B"
]
但我想这样:
[
"Car": "Ford",
"Value1": 13,
"Value2": "A"
,
"Train": "Bombardier",
"Value1": 14,
"Value2": "B"
]
我怎样才能做到这一点?我是否必须编写自定义JsonConverter
并手动完成所有操作?由于 DTO 类位于无法访问 Json.NET 的单独程序集中,因此无法使用特定的 Json.NET 属性。
我不一定要使用KeyValuePair<string, string>
。我只需要一个允许我拥有灵活属性名称的数据结构。
【问题讨论】:
.NET Core 3+ 中 JSON 的System.Text.Json
实现目前有很多限制,其中之一是它无法正确序列化 KeyValuePair<TKey, TValue>
属性。目前计划在 .NET 5.0(将于 2020 年 11 月发布)中解决此问题正在此处跟踪:github.com/dotnet/runtime/issues/30524
@silkfire 使用 Json.NET 序列化这两个实例时
我使用的是 .NET Framework,而不是 .NET Core
我必须写一个自定义的 JsonConverter - 他们真的没有那么复杂。但最简单的方法是像 Marc 在他的回答中建议的那样,只使用一个条目字典。
无法添加 Json.NET 属性但可以添加自己的自定义属性吗?
【参考方案1】:
您可以通过应用[JsonExtensionData]
轻松地将字典序列化为父对象的一部分,而不是KeyValuePair<>
,如下所示:
public class MyDto
[JsonExtensionData]
public Dictionary<string, object> Identifier get; set;
但是,您已声明 无法使用特定的 Json.NET 属性。 但是,由于您通常可以修改 DTO,因此您可以使用 自定义扩展数据属性 然后在 custom generic JsonConverter
或 custom contract resolver 中处理该属性。
首先,有关使用自定义扩展数据属性和自定义JsonConverter
的示例,请参阅JsonTypedExtensionData
从this answer 到How to deserialize a child object with dynamic (numeric) key names? p>
其次,如果您不想使用转换器,使用自定义合约解析器处理自定义扩展数据属性,首先定义以下合约解析器:
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false)]
public class MyJsonExtensionDataAttribute : Attribute
public class MyContractResolver : DefaultContractResolver
protected override JsonObjectContract CreateObjectContract(Type objectType)
var contract = base.CreateObjectContract(objectType);
if (contract.ExtensionDataGetter == null && contract.ExtensionDataSetter == null)
var dictionaryProperty = contract.Properties
.Where(p => typeof(IDictionary<string, object>).IsAssignableFrom(p.PropertyType) && p.Readable && p.Writable)
.Where(p => p.AttributeProvider.GetAttributes(typeof(MyJsonExtensionDataAttribute), false).Any())
.SingleOrDefault();
if (dictionaryProperty != null)
dictionaryProperty.Ignored = true;
contract.ExtensionDataGetter = o =>
((IDictionary<string, object>)dictionaryProperty.ValueProvider.GetValue(o)).Select(p => new KeyValuePair<object, object>(p.Key, p.Value));
contract.ExtensionDataSetter = (o, key, value) =>
var dictionary = (IDictionary<string, object>)dictionaryProperty.ValueProvider.GetValue(o);
if (dictionary == null)
dictionary = (IDictionary<string, object>)this.ResolveContract(dictionaryProperty.PropertyType).DefaultCreator();
dictionaryProperty.ValueProvider.SetValue(o, dictionary);
dictionary.Add(key, value);
;
contract.ExtensionDataValueType = typeof(object);
// TODO set contract.ExtensionDataNameResolver
return contract;
然后如下修改你的 DTO:
public class MyDto
[MyJsonExtensionData]
public Dictionary<string, object> Identifier get; set;
public double Value1 get; set;
public string Value2 get; set;
并按如下方式进行序列化,缓存解析器的静态实例以提高性能:
static IContractResolver resolver = new MyContractResolver();
// And later
var settings = new JsonSerializerSettings
ContractResolver = resolver,
;
var json = JsonConvert.SerializeObject(results, Formatting.Indented, settings);
注意事项:
MyJsonExtensionData
属性的类型必须可分配给类型 IDictionary<string, object>
并具有公共的无参数构造函数。
未实现扩展数据属性名称的命名策略。
Json.NET 在每个对象的末尾序列化扩展数据属性,而您的问题在开头显示自定义属性。由于 JSON 对象被 standard 定义为 一组无序的名称/值对,我认为这无关紧要。但是,如果您需要在对象的开头使用自定义属性,则可能需要使用自定义转换器而不是自定义合约解析器。
演示小提琴here.
【讨论】:
【参考方案2】:编辑:此答案适用于最初提出的问题;它随后被编辑过,现在可能没那么有用了
您可能需要使用具有单个元素的字典,即Dictionary<string,string>
,它确实以您想要的方式进行序列化;例如:
var obj = new Dictionary<string, string> "Car", "Ford" ;
var json = JsonConvert.SerializeObject(obj);
System.Console.WriteLine(json);
【讨论】:
我试过你的代码,但它序列化为"Identifier": "Car": "Ford"
,而不是 "Car": "Ford"
那是因为Identifier
是您的财产的名称。根据 default 行为,这是正确的。
这可能是真的,但这不是我需要的。
那么你需要一个自定义转换器,就像你自己建议的那样:)
@mu88 你会注意到我没有序列化一个对象 with 标识符是字典;我序列化了只是字典【参考方案3】:
由于您的 MyDto
类位于一个单独的程序集中,您可以对其进行的更改种类受到限制,那么是的,我认为您最好的选择是为该类创建一个自定义转换器。像这样的东西应该可以工作:
public class MyDtoConverter : JsonConverter
public override bool CanConvert(Type objectType)
return objectType == typeof(MyDto);
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
MyDto dto = (MyDto)value;
writer.WriteStartObject();
writer.WritePropertyName(dto.Identifier.Key);
writer.WriteValue(dto.Identifier.Value);
var contract = (JsonObjectContract)serializer.ContractResolver.ResolveContract(typeof(MyDto));
foreach (JsonProperty prop in contract.Properties.Where(p => p.PropertyName != nameof(MyDto.Identifier)))
writer.WritePropertyName(prop.PropertyName);
writer.WriteValue(prop.ValueProvider.GetValue(dto));
writer.WriteEndObject();
public override bool CanRead => false;
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
throw new NotImplementedException();
要使用它,您需要将转换器添加到您的 Web API 配置中:
config.Formatters.JsonFormatter.SerializerSettings.Converters.Add(new MyDtoConverter());
这是控制台应用程序中转换器的工作演示:https://dotnetfiddle.net/DksgMZ
【讨论】:
以上是关于将单个 KeyValuePair<> 序列化为 JSON 作为父对象的一部分的主要内容,如果未能解决你的问题,请参考以下文章
将 JSON 反序列化为 List<List<KeyValuePair>> 结果为空
从 IEnumerable<KeyValuePair<>> 重新创建字典
从 List<KeyValuePair<string,string>> 返回匹配