Reputation: 29
I've got an IEnumerable of either Tuple<string,string> or KeyValuePair<string,string> (I'm flattening a list of property values to get them)
I want it to json serialize like a dictionary where the key is the property but I've got duplicate keys after the flattening so I can't use dictionary.
so I want
{
"test": "value1",
"test": "value2",
"test": "value3",
"test2": "somevalue"
}
Here's how I'm coming up with the flattened list
Values.SelectMany(a => a.Value.Select(b => new KeyValuePair<string, string>(a.Key, b)));
What can I do to make it easy and just call JsonSerializer.Serialize on the output to get what I want?
Upvotes: 3
Views: 1714
Reputation: 22829
One way to solve this problem is via the usage of ILookup<,>
and a custom JsonConverter
.
ILookup<T1, T2>
as a Dictionary<T1, IEnumerable<T2>>
var dataSource = new List<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>("test", "value1"),
new KeyValuePair<string, string>("test", "value2"),
new KeyValuePair<string, string>("test", "value3"),
new KeyValuePair<string, string>("test2", "somevalue"),
};
var toBeSerializedData = dataSource.ToLookup(pair => pair.Key, pair => pair.Value);
var serializedData =JsonConvert.SerializeObject(toBeSerializedData);
This will generate the following json:
[
[
"value1",
"value2",
"value3"
],
[
"somevalue"
]
]
Key
s.In order to overcome of these we can define a custom JsonConverter
:
public class LookupSerializer : JsonConverter
{
public override bool CanConvert(Type objectType) => objectType.GetInterfaces()
.Any(a => a.IsGenericType && a.GetGenericTypeDefinition() == typeof(ILookup<,>));
public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer)
{
writer.WriteStartObject();
foreach (object values in (IEnumerable)value)
{
var keyProp = values.GetType().GetProperty("Key");
var keyValue = keyProp.GetValue(values, null);
foreach (var val in (IEnumerable)values)
{
writer.WritePropertyName(keyValue.ToString());
writer.WriteValue(val.ToString());
}
}
writer.WriteEndObject();
}
public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer)
=> throw new NotImplementedException();
}
CanConvert
: restricts the usage only for ILookup
.WriteJson
: iterates through the groups with the outer foreach
and iterates through the values via the inner foreach
.JObject
(and its Add
method) to create the output because it would fail with an ArgumentException
:Can not add property test to Newtonsoft.Json.Linq.JObject.
Property with the same name already exists on object.
JsonWriter
to construct the output.If you pass an instance of the LookupSerialzer
to the SerializeObject
(or register the converter application-wide):
var serializedData =JsonConvert.SerializeObject(toBeSerializedData, new LookupSerializer());
then the output will be the one as desired:
{
"test" : "value1",
"test" : "value2",
"test" : "value3",
"test2" : "somevalue"
}
Upvotes: 4