Serialize List<KeyValuePair<string, string>> as JSON
Asked Answered
B

3

51

I'm very new with JSON, please help!

I am trying to serialise a List<KeyValuePair<string, string>> as JSON

Currently:

[{"Key":"MyKey 1","Value":"MyValue 1"},{"Key":"MyKey 2","Value":"MyValue 2"}]

Expected:

[{"MyKey 1":"MyValue 1"},{"MyKey 2":"MyValue 2"}]

I referred to some examples from this and this.

This is my KeyValuePairJsonConverter : JsonConverter

public class KeyValuePairJsonConverter : JsonConverter
{
    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        List<KeyValuePair<object, object>> list = value as List<KeyValuePair<object, object>>;
        writer.WriteStartArray();
        foreach (var item in list)
        {
            writer.WriteStartObject();
            writer.WritePropertyName(item.Key.ToString());
            writer.WriteValue(item.Value.ToString());
            writer.WriteEndObject();
        }
        writer.WriteEndArray();
    }

    public override bool CanConvert(Type objectType)
    {
        return objectType == typeof(List<KeyValuePair<object, object>>);
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        var jsonObject = JObject.Load(reader);
        var target = Create(objectType, jsonObject);
        serializer.Populate(jsonObject.CreateReader(), target);
        return target;
    }

    private object Create(Type objectType, JObject jsonObject)
    {
        if (FieldExists("Key", jsonObject))
        {
            return jsonObject["Key"].ToString();
        }

        if (FieldExists("Value", jsonObject))
        {
            return jsonObject["Value"].ToString();
        }
        return null;
    }

    private bool FieldExists(string fieldName, JObject jsonObject)
    {
        return jsonObject[fieldName] != null;
    }
}

I am calling it from a WebService method like this

List<KeyValuePair<string, string>> valuesList = new List<KeyValuePair<string, string>>();
Dictionary<string, string> valuesDict = SomeDictionaryMethod();

foreach(KeyValuePair<string, string> keyValue in valuesDict)
{
    valuesList.Add(keyValue);
}

JsonSerializerSettings jsonSettings = new JsonSerializerSettings { Converters = new [] {new KeyValuePairJsonConverter()} };
string valuesJson = JsonConvert.SerializeObject(valuesList, jsonSettings);
Boccie answered 6/1, 2017 at 9:51 Comment(3)
Please try to change ´objectType == typeof(List<KeyValuePair<object, object>>);´ to ´objectType == typeof(List<KeyValuePair<string, string>>);´ in CanConvert methodLalittah
One question - why are you using list of KeyValuePairs instead of Dictionary?Rainbolt
@SergeyBerezovskiy you are right! I guess I got lost while googling what to use and ended up over complicating things. Thank you!Boccie
A
68

You can use Newtonsoft and dictionary:

    var dict = new Dictionary<int, string>();
    dict.Add(1, "one");
    dict.Add(2, "two");

    var output = Newtonsoft.Json.JsonConvert.SerializeObject(dict);

The output is :

{"1":"one","2":"two"}

Edit

Thanks to @Sergey Berezovskiy for the information.

You are currently using Newtonsoft, so just change your List<KeyValuePair<object, object>> to Dictionary<object,object> and use the serialize and deserialize method from the package.

Alphanumeric answered 6/1, 2017 at 10:47 Comment(6)
So he just have to change is List<KeyValuePair<object, object>> to Dictionnary<object,object>. I will edit my anwserAlphanumeric
That's what I asked him in comments. It's not always possible to change list of KeyValuePairs to dictionary. Example - you can use KVP to pass key-values as alternative to Tuple or creating class. And you can have many key-value pairs with same Key. Which is not true with dictionary.Rainbolt
In his create method, he change if the key already exist, so I think we are more in a scenario who needs a dictionnary, but you totaly right is some cases that kind of list can be usefull :)Alphanumeric
@Alphanumeric thank you for your help! I removed the List<KeyValuePair<string, string>> to simply Dictionary<string, string> and serialization just worked.Boccie
so easy when you know it. thanks. this answer would benefit greatly from explaining why... how is the keyvaluepair different from the dictionary to explain this difference in serialization behavior?Hathor
I disagree. List<KeyValuePair<object, object>> maintains insertion order while Dictionary does not.Guibert
G
6

So I didn't want to use anything but native c# to solve a similar issue and for reference this was using .net 4, jquery 3.2.1 and backbone 1.2.0.

My issues was that the List<KeyValuePair<...>> would process out of the controller into a backbone model but when I saved that model the controller could not bind List.

public class SomeModel {
    List<KeyValuePair<int, String>> SomeList { get; set; }
}

[HttpGet]
SomeControllerMethod() {
    SomeModel someModel = new SomeModel();
    someModel.SomeList = GetListSortedAlphabetically();
    return this.Json(someModel, JsonBehavior.AllowGet);
}

network capture:

"SomeList":[{"Key":13,"Value":"aaab"},{"Key":248,"Value":"aaac"}]

But even though this set SomeList properly in the backing model.js trying to save the model without any changes to it would cause the binding SomeModel object to have the same length as the parameters in the request body but all the keys and values were null:

[HttpPut]
SomeControllerMethod([FromBody] SomeModel){
    SomeModel.SomeList; // Count = 2, all keys and values null.
}

The only things I could find is that KeyValuePair is a structure and not something that can be instantiated in this manner. What I ended up doing is the following:

  • Add a Model wrapper somewhere that contains key, value fields:

    public class KeyValuePairWrapper {
        public int Key { get; set; }
        public String Value { get; set; }
    
        //default constructor will be required for binding, the Web.MVC binder will invoke this and set the Key and Value accordingly.
        public KeyValuePairWrapper() { }
    
        //a convenience method which allows you to set the values while sorting
        public KeyValuePairWrapper(int key, String value)
        {
            Key = key;
            Value = value;
        }
    }
    
  • Set up your binding class model to accept your custom wrapper object.

    public class SomeModel
    {
        public List<KeyValuePairWrapper> KeyValuePairList{ get; set }; 
    }
    
  • Get some json data out of a controller

    [HttpGet]
    SomeControllerMethod() {
        SomeModel someModel = new SomeModel();
        someModel.KeyValuePairList = GetListSortedAlphabetically();
        return this.Json(someModel, JsonBehavior.AllowGet);
    }
    
  • Do something at a later time, maybe model.save(null, ...) is invoked

    [HttpPut]
    SomeControllerMethod([FromBody] SomeModel){
        SomeModel.KeyValuePairList ; // Count = 2, all keys and values are correct.
    }
    
Glooming answered 20/9, 2017 at 1:55 Comment(0)
S
5

The answers before are correct but they're too heavy. They don't make enough use of Linq's one-liners.

Let's recap the context:

You start from this C# type :

List<KeyValuePair<string, string>>

...which, once serialized, becomes this Json:

[{"Key":"MyKey 1","Value":"MyValue 1"},{"Key":"MyKey 2","Value":"MyValue 2"}]

...but you actually want this Json:

[{"MyKey 1":"MyValue 1"},{"MyKey 2":"MyValue 2"}]

...which would require the following C# type :

List<Dictionary<string, string>>

So in essence you want to convert a C# type to another C# type :

List<KeyValuePair<string, string>>   -->   List<Dictionary<string, string>>

Note : The only slightly annoying part is this bit of the conversion :

KeyValuePair<string, string>  -->   Dictionary<string, string>

because to my knowledge there's no direct way of using a single KeyValuePair to create a Dictionary containing only it. You'll see in the solution that I first put that single KVP into a list containing only it, then used ToDictionary.

For demo, I recreated your C# input :

var input = new List<KeyValuePair<string, string>>
{
            new KeyValuePair<string, string>("My Key 1", "MyValue1"),
            new KeyValuePair<string, string>("My Key2", "MyValue2")
};

then you can convert like this (ONE-LINER SOLUTION) :

var converted = input
    .Select(singleKvp => (new List<KeyValuePair<string, string>>() { singleKvp }).ToDictionary(x => x.Key, x => x.Value))
    .ToList(); // Optional

You can test the result with :

var output = JsonSerializer.Serialize(converted);
Seidler answered 19/4, 2023 at 13:35 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.