问题
I have a list of objects that I want to serialize as JSON like below, since there is a complex type in this list. I want to change this complex type to Key/Value
pairs where each Key
is the name of a property in the type, and each Value
is the corresponding value of that property. I've tried multiple solutions but none of them worked for me.
Here is the object structure
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; }
}
Here is my desired output with two entries in IList<Metadata>
{
"Metadata": [{
"Key": "FirstName",
"Value": "ABC"
},
{
"Key": "LastName",
"Value": "XYZ"
},
{
"Key": "FirstName",
"Value": "DEF"
},
{
"Key": "LastName",
"Value": "MNL"
}
],
"Length": 25,
"Type": "application/mp3"
}
I know that JsonSerializer
does not change the face of the object by itself, so I tried to change it by implementing a custom 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);
}
}
}
And here is how I call it:
var serializedObject = JsonConvert.SerializeObject(listOfData, Formatting.Indented, new CustomMetadataConverter(typeof(Metadata)));
I tried this solution to move it to root, but the output of the custom converter is wrapped with the parent instead of replacing it. I know it's because the custom converter only reads children of Metadata
but if I change CustomMetadataConverter(typeof(Metadata))
to CustomMetadataConverter(typeof(Data))
it converts the whole query to Key/Value
pairs. And that's not what I want.
Here is the output after implementing custom converter
{
"Metadata": [
{
"Metadata": [
{
"Key": "FirstName",
"Value": "ABC"
},
{
"Key": "LastName",
"Value": "XYZ"
}
]
}
],
"Length": 25,
"Type": "application/mp3"
}
回答1:
If you have two or more Metadata
items in the list, and you serialized them to key-value pair objects in a flat array like you described in your question, then it would be difficult to tell which key-value pairs belong together if you need to deserialize the JSON later. It would be better to use a two-dimensional array structure like this instead:
{
"Metadata": [
[
{
"Key": "FirstName",
"Value": "ABC"
},
{
"Key": "LastName",
"Value": "XYZ"
}
],
[
{
"Key": "FirstName",
"Value": "DEF"
},
{
"Key": "LastName",
"Value": "MNL"
}
]
],
"Length": 25,
"Type": "application/mp3"
}
Here is a converter which will do that:
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;
}
}
You can use the converter like this:
var json = JsonConvert.SerializeObject(data, Formatting.Indented, new ObjectToKvpArrayConverter<Metadata>());
Here is a round-trip demo: https://dotnetfiddle.net/wx2e9d
来源:https://stackoverflow.com/questions/58568644/nested-nodes-are-not-formatted-correctly-after-implementing-jsonconverter