[英]Serialize single KeyValuePair<> to JSON as part of a parent object
I have a DTO class like this:我有一个像这样的 DTO class:
public class MyDto
{
public KeyValuePair<string, string> Identifier { get; set; }
public double Value1 { get; set; }
public string Value2 { get; set; }
}
And two instances could look like this:两个实例可能如下所示:
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"
},
};
When serializing the results within ASP.NET Web API (which uses Json.NET), the output looks like this:在 ASP.NET Web API(使用 Json.NET)中序列化结果时,Z78E62Z61F6393D19356
[
{
"Identifier": {
"Key": "Car",
"Value": "Ford"
},
"Value1": 13,
"Value2": "A"
},
{
"Identifier": {
"Key": "Train",
"Value": "Bombardier"
},
"Value1": 14,
"Value2": "B"
}
]
But I'd like to have it that way:但我想这样:
[
{
"Car": "Ford",
"Value1": 13,
"Value2": "A"
},
{
"Train": "Bombardier",
"Value1": 14,
"Value2": "B"
}
]
How can I achieve this?我怎样才能做到这一点? Do I have to write a custom
JsonConverter
and do everything by hand?我是否必须编写自定义
JsonConverter
并手动完成所有操作? Since the DTO class is located in a separate assembly not having access to Json.NET, the usage of specific Json.NET attributes is not possible.由于 DTO class 位于无法访问 Json.NET 的单独组件中,因此无法使用特定的 Json.NET 属性。
I'm not bound to using KeyValuePair<string, string>
.我不一定要使用
KeyValuePair<string, string>
。 I only need a data structure that allows me to have a flexible attribute name.我只需要一个允许我拥有灵活属性名称的数据结构。
Edit: this answer applies to the question as originally asked ;编辑:此答案适用于最初提出的问题; it has subsequently been edited, and may now be less useful
它随后被编辑,现在可能不太有用
You may need to use a dictionary that has a single element, ie Dictionary<string,string>
, which does serialize in the way you want;您可能需要使用具有单个元素的字典,即
Dictionary<string,string>
,它会以您想要的方式进行序列化; for example:例如:
var obj = new Dictionary<string, string> { { "Car", "Ford" } };
var json = JsonConvert.SerializeObject(obj);
System.Console.WriteLine(json);
Instead of a KeyValuePair<>
, you can easily serialize a dictionary as part of a parent object by applying [JsonExtensionData]
like so:而不是
KeyValuePair<>
,您可以通过应用[JsonExtensionData]
轻松地将字典序列化为父 object 的一部分,如下所示:
public class MyDto
{
[JsonExtensionData]
public Dictionary<string, object> Identifier { get; set; }
However, you have stated that the usage of specific Json.NET attributes is not possible.但是,您已声明无法使用特定的 Json.NET 属性。 But since you can generally modify your DTO you could mark it with a custom extension data attribute and then handle that attribute in either a custom generic
JsonConverter
or a custom contract resolver .但是由于您通常可以修改您的 DTO,您可以使用自定义扩展数据属性对其进行标记,然后在自定义通用
JsonConverter
或自定义合同解析器中处理该属性。
Firstly , for an example of using a custom extension data attribute with a custom JsonConverter
, see JsonTypedExtensionData
from this answer to How to deserialize a child object with dynamic (numeric) key names?首先,有关将自定义扩展数据属性与自定义
JsonConverter
一起使用的示例,请参阅此答案中的JsonTypedExtensionData
如何使用动态(数字)键名反序列化子 object?
Secondly , if you prefer not to use a converter, to handle a custom extension data attribute with a custom contract resolver, first define the following contract resolver:其次,如果您不想使用转换器,要使用自定义合约解析器处理自定义扩展数据属性,请首先定义以下合约解析器:
[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;
}
}
Then modify your DTO as follows:然后按如下方式修改您的 DTO:
public class MyDto
{
[MyJsonExtensionData]
public Dictionary<string, object> Identifier { get; set; }
public double Value1 { get; set; }
public string Value2 { get; set; }
}
And serialize as follows, caching a static instance of your resolver for performance:并按如下方式进行序列化,缓存解析器的 static 实例以提高性能:
static IContractResolver resolver = new MyContractResolver();
// And later
var settings = new JsonSerializerSettings
{
ContractResolver = resolver,
};
var json = JsonConvert.SerializeObject(results, Formatting.Indented, settings);
Notes:笔记:
The MyJsonExtensionData
property's type must be assignable to type IDictionary<string, object>
and have a public, parameterless constructor. MyJsonExtensionData
属性的类型必须可分配给类型IDictionary<string, object>
并具有公共的无参数构造函数。
Naming strategies for extension data property names are not implemented.未实现扩展数据属性名称的命名策略。
Json.NET serializes extension data attributes at the end of each object whereas your question shows the custom attributes at the beginning. Json.NET 在每个 object 的末尾序列化扩展数据属性,而您的问题在开头显示自定义属性。 Since a JSON object is defined to be an unordered set of name/value pairs by the standard I think this should not matter.
由于标准将 JSON object 定义为一组无序的名称/值对,因此我认为这无关紧要。 But if you require the custom properties at the beginning of your object, you may need to use a custom converter rather than a custom contract resolver.
但是,如果您需要 object 开头的自定义属性,则可能需要使用自定义转换器而不是自定义合约解析器。
Since your MyDto
class is in a separate assembly for which you have limitations in the kinds of changes you can make, then yes, I think your best bet is to create a custom converter for the class.由于您的
MyDto
class 位于单独的组件中,因此您可以进行的更改种类受到限制,那么是的,我认为您最好的选择是为 class 创建一个自定义转换器。 Something like this should work:像这样的东西应该工作:
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();
}
}
To use it you will need to add the converter to your Web API configuration:要使用它,您需要将转换器添加到 Web API 配置中:
config.Formatters.JsonFormatter.SerializerSettings.Converters.Add(new MyDtoConverter());
Here is a working demo of the converter in a console app: https://dotnetfiddle.net/DksgMZ这是控制台应用程序中转换器的工作演示: https://dotnetfiddle.net/DksgMZ
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.