3

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?

6
  • Is it valid to have JSON with multiple keys with the same value? Commented Feb 16, 2021 at 4:08
  • Try ToDictionary Commented Feb 16, 2021 at 4:09
  • 1
    See if this helps: stackoverflow.com/questions/30353840/… Commented Feb 16, 2021 at 4:10
  • 2
    @mjwills It will throw exception. Duplicating keys are not permitted in json, so you have to handle it. But it's possible to use DisctinctBy, which is not included into default LINQ Commented Feb 16, 2021 at 4:13
  • 3
    @JL0PD You may want to read what the OP wants. Since the OP specifically wants duplicate keys. ToDictionary and DistinctBy won't help. Commented Feb 16, 2021 at 4:50

1 Answer 1

4

One way to solve this problem is via the usage of ILookup<,> and a custom JsonConverter.

  • You can think of the ILookup<T1, T2> as a Dictionary<T1, IEnumerable<T2>>
  • So, it is a Bag data structure.
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" ] ] 
  • As you see the values are grouped by the Keys.
  • But the keys are omitted and the values are in arrays.

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.
  • Here we can't use 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.

  • So, we have to use lower level 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" } 
Sign up to request clarification or add additional context in comments.

1 Comment

Thanks! I hate having to duplicate the fields but I can't break old systems right now. This is exactly what I'll need to do!

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.