实现JsonConverter后,嵌套节点的格式不正确



我有一个对象列表,我想将其序列化为JSON,如下所示,因为这个列表中有一个复杂的类型。我想将这个复杂类型更改为Key/Value对,其中每个Key是类型中某个属性的名称,每个Value是该属性的相应值。我尝试过多种解决方案,但都不适用。

这是对象结构

public class Metadata
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
public class Data
{
// If I change the type of Metadata to IList<IDictionary<string, object>>
// custom converter won't catch it at all when I pass it to its constructor
//public IList<IDictionary<string, object>> Metadata { get; set; }
public IList<Metadata> Metadata { get; set; }
public int Length { get; set; }
public string Type { get; set; }
}

这是我想要的输出,IList<Metadata>中有两个条目

{
"Metadata": [{
"Key": "FirstName",
"Value": "ABC"
},
{
"Key": "LastName",
"Value": "XYZ"
},
{
"Key": "FirstName",
"Value": "DEF"
},
{
"Key": "LastName",
"Value": "MNL"
}
],
"Length": 25,
"Type": "application/mp3"
}

我知道JsonSerializer本身不会改变对象的外观,所以我尝试通过实现自定义JsonConverter:来改变它

public class KeyValue
{
public string Key { get; set; }
public string Value { get; set; }
}
class CustomMetadataConverter : JsonConverter
{
private readonly Type[] _types;
public CustomMetadataConverter(params Type[] types)
{
_types = types;
}
public override bool CanConvert(Type objectType)
{
return _types.Any(t => t == objectType);
}
// I've removed ReadJson and CanRead here to keep the question clear
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
JToken token = JToken.FromObject(value);
if (token.Type != JTokenType.Object)
token.WriteTo(writer);
else
{
JObject jsonObject = (JObject)token;
IList<KeyValue> properties = jsonObject.Properties()
.Select(p => new KeyValue { Key = p.Name, Value = p.Value.ToString() }).ToList();
// If I change the IList<KeyValue> to anonymous array, output would be the same
//var properties = jsonObject.Properties().Select(p => new { Key = p.Name, Value = p.Value.ToString() }).ToArray();
jsonObject.RemoveAll();
jsonObject.Add(new JProperty("Metadata", JToken.FromObject(properties)));
jsonObject.WriteTo(writer);
}
}
}

我是这样称呼它的:

var serializedObject = JsonConvert.SerializeObject(listOfData, Formatting.Indented, new CustomMetadataConverter(typeof(Metadata)));

我尝试了这个解决方案将其移动到根目录,但自定义转换器的输出用父目录包装,而不是替换它。我知道这是因为自定义转换器只读取Metadata的子目录,但如果我将CustomMetadataConverter(typeof(Metadata))更改为CustomMetadataConverter(typeof(Data)),它会将整个查询转换为Key/Value对。这不是我想要的。

这是实现自定义转换器后的输出

{
"Metadata": [
{
"Metadata": [
{
"Key": "FirstName",
"Value": "ABC"
},
{
"Key": "LastName",
"Value": "XYZ"
}
]
}
],
"Length": 25,
"Type": "application/mp3"
}

如果列表中有两个或多个Metadata项,并且像在问题中描述的那样将它们序列化为平面数组中的键值对对象,那么如果以后需要反序列化JSON,则很难判断哪些键值对属于同一组。最好使用这样的二维阵列结构:

{
"Metadata": [
[
{
"Key": "FirstName",
"Value": "ABC"
},
{
"Key": "LastName",
"Value": "XYZ"
}
],
[
{
"Key": "FirstName",
"Value": "DEF"
},
{
"Key": "LastName",
"Value": "MNL"
}
]
],
"Length": 25,
"Type": "application/mp3"
}

这里有一个转换器可以做到这一点:

class ObjectToKvpArrayConverter<T> : JsonConverter where T : class
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(T);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
JArray array = new JArray(
JObject.FromObject(value)
.Properties()
.Select(jp => 
new JObject(
new JProperty("Key", jp.Name),
new JProperty("Value", jp.Value)
)
)
);
array.WriteTo(writer);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JObject obj = new JObject(
JArray.Load(reader)
.Children<JObject>()
.Select(jo => new JProperty((string)jo["Key"], jo["Value"]))
);
T result = Activator.CreateInstance<T>();
serializer.Populate(obj.CreateReader(), result);
return result;
}
}

你可以这样使用转换器:

var json = JsonConvert.SerializeObject(data, Formatting.Indented, new ObjectToKvpArrayConverter<Metadata>());

以下是一个往返演示:https://dotnetfiddle.net/wx2e9d

最新更新