-1

I need to create a C# class that matches this json with the exact brackets like this.

{
    "data": {
        "a": "4",
        "b": "2",
        "c": "3",
        "a": "444",
    },
}

System.Collections.Generic.Dictionary<string, string> does it, but it does not allow multiple entries with the same key, so that won't work.

List<T> with a custom data class, string[2], tuples or valueTuples or KeyValuePair<string, string> creates different json where each entry gets "item1", "item1" or different brackets.

How can I deserialize and serialize this exact JSON? It's a customer's "standard" that I must support.

dbc
  • 104,963
  • 20
  • 228
  • 340
Otterprinz
  • 459
  • 3
  • 10
  • 5
    It's not valid JSON with the same key being used multiple times. – NineBerry Jun 21 '21 at 14:01
  • 4
    well, _technically_ [JSON does not _explicitly_ forbid duplicate key names](https://stackoverflow.com/a/21833017/5309228). but it doesn't explicitly forbid clogging your own toilet with toilet paper and epoxy. so you _could_ try a `List>` - or you could fix your format so it makes a lot more sense. but maybe you could shed a little light on _why_ you want duplicate keys? – Franz Gleichmann Jun 21 '21 at 14:05
  • 1
    @NineBerry That explains, why it is so hard to find useful examples. Thanks! – Otterprinz Jun 21 '21 at 14:24
  • 1
    Does this answer your question? [Creating json object with duplicate keys in C#](https://stackoverflow.com/q/64014322/3744182). It may not since that converter makes duplicate keys be adjacent. – dbc Jun 21 '21 at 14:24
  • 1
    And this looks like a perfect duplicate for serialization: [c# custom Dictionary that accepts duplicate keys for serialization](https://stackoverflow.com/q/30353840/3744182). Do you need deserialization as well? – dbc Jun 21 '21 at 14:27
  • 1
    @FranzGleichmann I don't want it. It's a customers "standard". – Otterprinz Jun 21 '21 at 14:30
  • 4
    Not sure why this got closed as "needs details or clarity", it's perfectly clear (even if not recommended as per RFC 8259). Do those duplicates answer your question? – dbc Jun 21 '21 at 14:40
  • @dbc The post you linked does not exactly answer the question, but this makes it rather clear that the solution i try to get is rather unusual, so i guess i need to go the way of a custom JsonConverter and the post you linked has an example of that, so it should be fine. Thanks for your help! – Otterprinz Jun 21 '21 at 14:51
  • @Otterprinz what i'd do in your situation is: tell the customer that what they _want_ is stupid, and they should change their system. – Franz Gleichmann Jun 21 '21 at 15:01
  • Interestingly, if you use the Newtonsoft JSON library, and you deserialize this to dynamic, it it happy to read it. However, it throws away the first value of `a`, leaving you with only three properties of `"data"`, one named `"b"`, one named `"c"` and `"a" : "444"`. If you get this to work, it will be fragile. You may want to take a more tactful version of @FranzGleichmann's suggestion – Flydog57 Jun 21 '21 at 15:06
  • What do you really need - to create a class or convert it to c# data object? If you need to create a class I can write it right now : public class DataClass { public object data {get; set;} } – Serge Jun 21 '21 at 16:56
  • Your JSON has ***trailing commas*** after the `"444",` and at the end of the data object `},`. May I assume you don't actually need those? – dbc Jun 21 '21 at 17:34
  • @Serge - that will not work because Json.NET will only retain the final `"a"` property. The first one will get overwritten. – dbc Jun 21 '21 at 17:40
  • Thank you. but I didn't mean deserialization. I just mean that the data can be kept as object. – Serge Jun 21 '21 at 17:44

1 Answers1

2

While not technically malformed, JSON objects with duplicated property names are not recommended by most recent JSON RFC, RFC 8259:

An object structure is represented as a pair of curly brackets surrounding zero or more name/value pairs (or members)...

An object whose names are all unique is interoperable in the sense that all software implementations receiving that object will agree on the name-value mappings. When the names within an object are not unique, the behavior of software that receives such an object is unpredictable. Many implementations report the last name/value pair only. Other implementations report an error or fail to parse the object, and some implementations report all of the name/value pairs, including duplicates.

You may wish to suggest an alternate JSON format to your customer, such as an array of single key-value pair objects.

That being said, if you must serialize and deserialize objects with duplicate properties, Json.NET will not do this out of the box, and you will have to create a custom JsonConverter to do it manually. Since the JSON object's values are all of the same type (here strings), you could bind to a List<KeyValuePair<string, string>>.

First, define the following model:

public class Model
{
    [JsonConverter(typeof(KeyValueListAsObjectConverter<string>))]
    public List<KeyValuePair<string, string>> data { get; } = new ();
}

And the following generic JsonConverter<List<KeyValuePair<string, TValue>>>:

public class KeyValueListAsObjectConverter<TValue> : JsonConverter<List<KeyValuePair<string, TValue>>>
{
    public override List<KeyValuePair<string, TValue>> ReadJson(JsonReader reader, Type objectType, List<KeyValuePair<string, TValue>> existingValue, bool hasExistingValue, JsonSerializer serializer)
    {
        if (reader.MoveToContentAndAssert().TokenType == JsonToken.Null)
            return null;
        reader.AssertTokenType(JsonToken.StartObject);
        var list = existingValue ?? (List<KeyValuePair<string, TValue>>)serializer.ContractResolver.ResolveContract(objectType).DefaultCreator();
        while (reader.ReadToContentAndAssert().TokenType != JsonToken.EndObject)
        {
            var name = (string)reader.AssertTokenType(JsonToken.PropertyName).Value;
            var value = serializer.Deserialize<TValue>(reader.ReadToContentAndAssert());
            list.Add(new KeyValuePair<string, TValue>(name, value));
        }
        return list;
    }

    public override void WriteJson(JsonWriter writer, List<KeyValuePair<string, TValue>> value, JsonSerializer serializer)
    {
        writer.WriteStartObject();
        foreach (var pair in value)
        {
            writer.WritePropertyName(pair.Key);
            serializer.Serialize(writer, pair.Value);
        }
        writer.WriteEndObject();
    }
}

public static partial class JsonExtensions
{
    public static JsonReader AssertTokenType(this JsonReader reader, JsonToken tokenType) => 
        reader.TokenType == tokenType ? reader : throw new JsonSerializationException(string.Format("Unexpected token {0}, expected {1}", reader.TokenType, tokenType));
    
    public static JsonReader ReadToContentAndAssert(this JsonReader reader) =>
        reader.ReadAndAssert().MoveToContentAndAssert();

    public static JsonReader MoveToContentAndAssert(this JsonReader reader)
    {
        if (reader == null)
            throw new ArgumentNullException();
        if (reader.TokenType == JsonToken.None)       // Skip past beginning of stream.
            reader.ReadAndAssert();
        while (reader.TokenType == JsonToken.Comment) // Skip past comments.
            reader.ReadAndAssert();
        return reader;
    }

    public static JsonReader ReadAndAssert(this JsonReader reader)
    {
        if (reader == null)
            throw new ArgumentNullException();
        if (!reader.Read())
            throw new JsonReaderException("Unexpected end of JSON stream.");
        return reader;
    }
}

And you will be able to deserialize and re-serialize the JSON shown in your question.

Demo fiddle here.

dbc
  • 104,963
  • 20
  • 228
  • 340