1

I am creating a json string by serializing an object in C# (using Newtonsoft) to pass to a third party charting library, so have no control over the structure I need to create. The structure requires an object with duplicate keys, something like;

{ "color": "dd3333",
  "linewidth": 2,
  "dataset": [ 3,4,5,6 ],
  "dataset": [ 5,6,7,8]
}

I'm struggling to get the output I want. I was using a dictionary, but have just come across the need for duplicate keys, which scuppers that. I've had a look at creating my own object to support duplicate keys (starting with these suggestions Duplicate keys in .NET dictionaries?) but am still struggling to get them to serialize to the format I need.

Any suggestions would be very welcome!

Nick
  • 141
  • 1
  • 7
  • 4
    That would be invalid JSON. The major JSON serializers won't produce it. Keys must be unique. If you can, redefine it to use an array of arrays: `"datasets": [[ 3,4,5,6 ], [ 5,6,7,8 ]]` Also, strict JSON compliance requires quoted key names. – madreflection Sep 22 '20 at 17:04
  • I'd like to recommend against this. According to [RFC 8259](https://tools.ietf.org/html/rfc8259#section-4): *The names within an object SHOULD be unique.*. If you really need this, you will need to create your own custom `JsonConverter` for the containing class. An example of custom parsing (not serialization) of duplicate keys is here: [How to deserialize JSON with duplicate property names in the same object](https://stackoverflow.com/q/20714160/3744182). – dbc Sep 22 '20 at 17:44
  • Thanks for the comment on the names not being in quotes, that was just my mistake, they should have been. I've edited the question. As I said above, I have no choice in providing this structure, I am providing data to a third party app and that's the defined structure. I agree it's not ideal, but hey ho, I'm stuck with it. As to whether it's valid JSON, that seems up for debate. A good discussion on this here https://stackoverflow.com/questions/21832701/does-json-syntax-allow-duplicate-keys-in-an-object – Nick Sep 22 '20 at 18:43
  • Thanks for the comments on deserializing, I'd already seen that. Unfortunately I want to serialize the object, the third party app de-serializes, so not my problem! – Nick Sep 22 '20 at 18:45
  • @Nick that discussion (Feb 2014) is from before RFC 8259 (Dec 2017) stated that _"the names within an object SHOULD be unique."_ What library are you using? Requiring something that violates an explicitly stated standard is bad design. – Pranav Hosangadi Sep 22 '20 at 18:45
  • I'd rather not name and shame the components we're using Pranav, with the exception of this issue they've been great. I suspect the particular data definition was defined before this change in the standards and they're stuck with it now there's a userbase out there. – Nick Sep 22 '20 at 18:49

1 Answers1

2

To solve this, I created a dictionary object that accepted duplicate keys (copied from Duplicate keys in .NET dictionaries?), and added a JsonConverter to control how the object was serialized;

  public class MultiMap<TKey, TValue>
  {
    private readonly Dictionary<TKey, IList<TValue>> storage;

    public MultiMap()
    {
      storage = new Dictionary<TKey, IList<TValue>>();
    }

    public void Add(TKey key, TValue value)
    {
      if (!storage.ContainsKey(key)) storage.Add(key, new List<TValue>());
      storage[key].Add(value);
    }

    public IEnumerable<TKey> Keys
    {
      get { return storage.Keys; }
    }

    public bool ContainsKey(TKey key)
    {
      return storage.ContainsKey(key);
    }

    public IList<TValue> this[TKey key]
    {
      get
      {
        if (!storage.ContainsKey(key))
          throw new KeyNotFoundException(
              string.Format(
                  "The given key {0} was not found in the collection.", key));
        return storage[key];
      }
    }
  }

The JsonConverter object was as follows (I didn't bother with the read as I didn't need it, but it could be easily implemented);

public class MultiMapJsonConverter<TKey, TValue> : JsonConverter
  {
    public override bool CanConvert(Type objectType)
    {
      return objectType == typeof(MultiMap<TKey, TValue>);
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
      throw new NotImplementedException("Unnecessary because CanRead is false. The type will skip the converter.");
    }

    public override bool CanRead
    {
      get { return false; }
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
      writer.WriteStartObject();
      MultiMap<TKey, TValue> m = (MultiMap<TKey, TValue>)value;

      foreach (TKey key in m.Keys)
      {
        foreach (TValue val in m[key])
        {
          writer.WritePropertyName(key.ToString());
          JToken.FromObject(val).WriteTo(writer);
        }
      }
      writer.WriteEndObject();
    }
  }

With this defined , the following code;

var mm = new MultiMap<string, object>();
mm.Add("color", "dd3333");
mm.Add("linewidth", 2);
mm.Add("dataset", new int[] { 3,4,5,6 });
mm.Add("dataset", new int[] { 5,6,7,8 });
var json = JsonConvert.SerializeObject(mm, new JsonConverter[] { new MultiMapJsonConverter<string, object>() });

gives me the json output;

{"color":"dd3333","linewidth":2,"dataset":[3,4,5,6],"dataset":[5,6,7,8]}
Nick
  • 141
  • 1
  • 7