JSON Serialize List<KeyValuePair<string, object>> - c#

I used a Dictionary in a Web API project, which is serializing like that in JSON:
{"keyname":{objectvalue},"keyname2:"....
Since I have duplicate keys I could't use Dictionary type any more, and instead now I'm using List<KeyValuePair<string,object>>.
But this is serializing that way:
[{"Key":"keyname","Value":"objectvalue"}...
Is there a way to have the List<KeyValuePair> serialize the same way a dictionary does?
Thanks.

If you use the Newtonsoft Json.NET library you can do the following.
Define a converter to write the list of key/value pairs the way you want:
class MyConverter : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
List<KeyValuePair<string, object>> list = value as List<KeyValuePair<string, object>>;
writer.WriteStartArray();
foreach (var item in list)
{
writer.WriteStartObject();
writer.WritePropertyName(item.Key);
writer.WriteValue(item.Value);
writer.WriteEndObject();
}
writer.WriteEndArray();
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
// TODO...
}
public override bool CanConvert(Type objectType)
{
return objectType == typeof(List<KeyValuePair<string, object>>);
}
}
Then use the converter:
var keyValuePairs = new List<KeyValuePair<string, object>>
{
new KeyValuePair<string, object>("one", 1),
new KeyValuePair<string, object>("two", 2),
new KeyValuePair<string, object>("three", 3)
};
JsonSerializerSettings settings = new JsonSerializerSettings { Converters = new [] {new MyConverter()} };
string json = JsonConvert.SerializeObject(keyValuePairs, settings);
This generates [{"one":1},{"two":2},{"three":3}]

Related

Json.NET deserialization of Tuple<...> inside another type doesn't work?

Using Json.net, deserializing a type that contains a Tuple<...> doesn't work (serialization works, but deserialization doesn't):
[TestMethod]
public void Test()
{
var orig = new TupleHolder("what????", true);
var json = JsonConvert.SerializeObject(orig);
Assert.AreEqual("{\"Tup\":{\"Item1\":\"what????\",\"Item2\":true}}", json);
// great! serialization works like a charm! now let's test deserialization:
var dupl = JsonConvert.DeserializeObject<TupleHolder>(json);
Assert.AreEqual("ZZZ", dupl.Tup.Item1); // pass! but it should be "what????"... what????
Assert.AreEqual(false, dupl.Tup.Item2); // pass! but it should be "true", right???
Assert.AreEqual(orig.Tup.Item1, dupl.Tup.Item1); // fail!
Assert.AreEqual(orig.Tup.Item2, dupl.Tup.Item2); // fail!
}
public class TupleHolder
{
public Tuple<string, bool> Tup { get; set; }
public TupleHolder() { Tup = new Tuple<string, bool>("ZZZ", false); }
public TupleHolder(string s, bool b) { Tup = new Tuple<string, bool>(s, b); }
}
Funny thing is that direct deserialization of Tuple<...> does work:
[TestMethod]
public void Test2()
{
var orig = new Tuple<string, bool>("ABC", true);
var json = JsonConvert.SerializeObject(orig);
var dupl = JsonConvert.DeserializeObject<Tuple<string, bool>>(json);
Assert.AreEqual(orig, dupl); // direct deserialization of Tuple<...> works.
}
Is it a Json.NET bug or am I missing here something?
The answer provided by Remi helped me. I took his TupleConverter and made it generic for a 2-tuple. The concept is the same for any N-tuple.
I leave it here in case it helps someone.
public class TupleConverter<U, V> : Newtonsoft.Json.JsonConverter
{
public override bool CanConvert(Type objectType)
{
return typeof(Tuple<U, V>) == objectType;
}
public override object ReadJson(
Newtonsoft.Json.JsonReader reader,
Type objectType,
object existingValue,
Newtonsoft.Json.JsonSerializer serializer)
{
if (reader.TokenType == Newtonsoft.Json.JsonToken.Null)
return null;
var jObject = Newtonsoft.Json.Linq.JObject.Load(reader);
var target = new Tuple<U, V>(
jObject["m_Item1"].ToObject<U>(), jObject["m_Item2"].ToObject<V>());
return target;
}
public override void WriteJson(Newtonsoft.Json.JsonWriter writer, object value, Newtonsoft.Json.JsonSerializer serializer)
{
serializer.Serialize(writer, value);
}
}
Note: My Tuple was JSON serialized with m_Item1 and m_Item2, so I had to change jObject["ItemX"] to jObject["m_ItemX"]
Usage example with a List<Tuple<int, User>>:
string result = "String to deserialize";
JsonSerializerSettings settings = new JsonSerializerSettings();
settings.Converters.Add(new TupleConverter<int, User>());
List<Tuple<int, User>> users = JsonConvert.DeserializeObject<List<Tuple<int, User>>>(result, settings);
The solution - or mine, anyhow - is to define a custom converter for the Tuple.
This example provides a concrete solution for a specific Tuple, but you could genericize it to make the TupleConverter class to handle any combination of value types. Could also make it abstract and have derived types implement instantiation methods for each item, to handle tuples with reference types.
public class TupleConverter : Newtonsoft.Json.JsonConverter
{
public override bool CanConvert(Type objectType)
{
return typeof(Tuple<string, bool>) == objectType;
}
public override object ReadJson(
Newtonsoft.Json.JsonReader reader,
Type objectType,
object existingValue,
Newtonsoft.Json.JsonSerializer serializer)
{
if (reader.TokenType == Newtonsoft.Json.JsonToken.Null)
return null;
var jObject = Newtonsoft.Json.Linq.JObject.Load(reader);
var target = new Tuple<string, bool>(
(string)jObject["Item1"], (bool)jObject["Item2"]);
return target;
}
public override void WriteJson(Newtonsoft.Json.JsonWriter writer, object value, Newtonsoft.Json.JsonSerializer serializer)
{
serializer.Serialize(writer, value);
}
}
public class TupleHolder
{
[Newtonsoft.Json.JsonConverter(typeof(TupleConverter))]
public Tuple<string, bool> Tup { get; set; }
public TupleHolder() { Tup = new Tuple<string, bool>("ZZZ", false); }
public TupleHolder(string s, bool b) { Tup = new Tuple<string, bool>(s, b); }
}
[Test]
public void Test()
{
var orig = new TupleHolder("what????", true);
var json = Newtonsoft.Json.JsonConvert.SerializeObject(orig);
Assert.AreEqual("{\"Tup\":{\"Item1\":\"what????\",\"Item2\":true}}", json);
var dupl = Newtonsoft.Json.JsonConvert.DeserializeObject<TupleHolder>(json);
// These succeed, now
Assert.AreEqual(orig.Tup.Item1, dupl.Tup.Item1);
Assert.AreEqual(orig.Tup.Item2, dupl.Tup.Item2);
}
I ended up with something more generic, hope it helps
public class TupleConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
var match = Regex.Match(objectType.Name, "Tuple`([0-9])", RegexOptions.IgnoreCase);
return match.Success;
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null)
return null;
try
{
var tupleTypes = objectType.GetProperties().ToList().Select(p => p.PropertyType).ToArray();
var jObject = Newtonsoft.Json.Linq.JObject.Load(reader);
var valueItems = new List<object>();
for (var i = 1; i <= tupleTypes.Length; i++)
valueItems.Add(jObject[$"m_Item{i}"].ToObject(tupleTypes[i - 1]));
var convertedObject = objectType.GetConstructor(tupleTypes)?.Invoke(valueItems.ToArray());
return convertedObject;
}
catch (Exception ex)
{
throw new Exception("Something went wrong in this implementation", ex);
}
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
serializer.Serialize(writer, value);
}
}

Change JSON.net content via property name?

I have this structure :
List<dynamic> lst = new List<dynamic>();
lst.Add(new{objId = 1,myOtherColumn = 5});
lst.Add(new{objId = 2,myOtherColumn = 6});
lst.Add(new{lala = "asd" ,lala2 = 7});
I'm serializing it via :
string st= JsonConvert.SerializeObject(lst);
Question:
How can I cause the serializer to change only values of the "objId" property , to something else ?
I know I should use class Myconverter : JsonConverter , but I didn't find any example which keeps the default behavior and in addition - allow me to add condition logic of serialization.
Here's a converter that will handle it, at least for simple objects as per your example. It looks for objects containing objId properties and then serialises all properties it finds on them. You may need to expand it to deal with other member types/more complex properties as required:
class MyConverter : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
writer.WriteStartObject();
foreach (var prop in value.GetType().GetProperties()) {
writer.WritePropertyName(prop.Name);
if (prop.Name == "objId") {
//modify objId values for example
writer.WriteValue(Convert.ToInt32(prop.GetValue(value, null)) + 10);
} else {
writer.WriteValue(prop.GetValue(value, null));
}
}
writer.WriteEndObject();
}
public override bool CanConvert(Type objectType)
{
//only attempt to handle types that have an objId property
return (objectType.GetProperties().Count(p => p.Name == "objId") == 1);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
Alternatively, you can use a converter that specifies it will only convert int types, then query where in the JSON path you are before doing any conversions. This has the benefit of not needing to deal with all the other members of the anonymous type.
class MyConverter : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
if (writer.Path.EndsWith(".objId")) {
writer.WriteValue(Convert.ToInt32(value) + 10);
}
else {
writer.WriteValue(value);
}
}
public override bool CanConvert(Type objectType)
{
return objectType == typeof (int);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}

How to Deserialize JSON with JavaScriptSerializer to Tuples

I'm looking way to Deserialize JSON string to c# List<Tuple<string, string>>.
"[{\"name\":\"OkeyTablePaired\",\"value\":\"true\"},
{\"name\":\"OkeyTableIndicator\",\"value\":\"true\"},
{\"name\":\"OkeyTableHued\",\"value\":\"true\"},
{\"name\":\"OkeyTableSpectatorQuiet\",\"value\":\"true\"},
{\"name\":\"OkeyTableEveryoneQuiet\",\"value\":\"true\"}]"
Tuple List:
List<Tuple<string, string>> tupleJson = new List<Tuple<string, string>>();
I would like to put them together as
[OkeyTablePaired]:[true]
[OkeyTableIndicator]:[false]
[OkeyTableHued]:[true]
[OkeyTableSpectatorQuiet]:[true]
[OkeyTableEveryoneQuiet]:[true]
in the List Tuple...
Any help would be fantastic.
Thanks.
This should work. Note that you need to convert the input to valid json array by adding brackets [] first. You will need to get JSON.NET to make this work.
//using System;
//using System.Collections.Generic;
//using System.Linq;
//using Newtonsoft.Json.Linq;
string validJson = "[" + json + "]";
JArray jsonArray = JArray.Parse(validJson);
List<Tuple<string, string>> tupleJson = jsonArray
.Select(p => new Tuple<string, string>((string)p["name"], (string)p["value"]))
.ToList();
More info in the documentation.
Assuming you get a valid JSON array, a custom converter with JSON.NET would work as well:
public class TupleConverter : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override bool CanWrite
{
get { return false; }
}
public override bool CanConvert(Type objectType)
{
return objectType == typeof(List<Tuple<string, string>>);
}
public override object ReadJson(
JsonReader reader,
Type objectType,
object existingValue,
JsonSerializer serializer)
{
List<Tuple<string, string>> result = null;
if (reader.TokenType == JsonToken.StartArray)
{
JArray deserialized = JArray.Load(reader);
result = new List<Tuple<string, string>>(deserialized.Count);
foreach (var token in deserialized)
{
if (token.Type == JTokenType.Object)
{
result.Add(Tuple.Create(
token["name"].ToObject<string>(),
token["value"].ToObject<string>()));
}
}
}
return result;
}
}
Usage:
List<Tuple<string, string>> result =
JsonConvert.DeserializeObject<List<Tuple<string, string>>>(json, new TupleConverter());
Example: https://dotnetfiddle.net/TEbNsH
If you've created a data contract for Tuple that the JSON can interpret, you can use DataContractJsonSerializer (from the System.Runtime.Serialization.Json library):
var request = WebRequest.Create(requestUrl) as HttpWebRequest;
request.Method = "GET";
var jsonSerializer = new DataContractJsonSerializer(typeof (Tuple));
var objResponse = (Tuple) jsonSerializer.ReadObject(response.GetResponseStream());
The data contract, in your case, would probably be pretty straightforward, something like this:
[DataContract]
public class Tuple
{
[DataMember]
public string OkeyTablePaired {get; set;}
[DataMember]
public string OkeyTableIndicator {get; set;}
.....etc.
}

How can I serialize/deserialize a dictionary with custom keys using Json.Net?

I have the following class, that I use as a key in a dictionary:
public class MyClass
{
private readonly string _property;
public MyClass(string property)
{
_property = property;
}
public string Property
{
get { return _property; }
}
public override bool Equals(object obj)
{
MyClass other = obj as MyClass;
if (other == null) return false;
return _property == other._property;
}
public override int GetHashCode()
{
return _property.GetHashCode();
}
}
The test I am running is here:
[Test]
public void SerializeDictionaryWithCustomKeys()
{
IDictionary<MyClass, object> expected = new Dictionary<MyClass, object>();
expected.Add(new MyClass("sth"), 5.2);
JsonSerializerSettings jsonSerializerSettings = new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.All };
string output = JsonConvert.SerializeObject(expected, Formatting.Indented, jsonSerializerSettings);
var actual = JsonConvert.DeserializeObject<IDictionary<MyClass, object>>(output, jsonSerializerSettings);
CollectionAssert.AreEqual(expected, actual);
}
The test fails, because Json.Net seems to be using the ToString() method on the dictionary keys, instead of serializing them properly. The resulting json from the test above is:
{
"$type": "System.Collections.Generic.Dictionary`2[[RiskAnalytics.UnitTests.API.TestMarketContainerSerialisation+MyClass, RiskAnalytics.UnitTests],[System.Object, mscorlib]], mscorlib",
"RiskAnalytics.UnitTests.API.TestMarketContainerSerialisation+MyClass": 5.2
}
which is clearly wrong. How can I get it to work?
This should do the trick:
Serialization:
JsonConvert.SerializeObject(expected.ToArray(), Formatting.Indented, jsonSerializerSettings);
By calling expected.ToArray() you're serializing an array of KeyValuePair<MyClass, object> objects rather than the dictionary.
Deserialization:
JsonConvert.DeserializeObject<KeyValuePair<IDataKey, object>[]>(output, jsonSerializerSettings).ToDictionary(kv => kv.Key, kv => kv.Value);
Here you deserialize the array and then retrieve the dictionary with .ToDictionary(...) call.
I'm not sure if the output meets your expectations, but surely it passes the equality assertion.
Grx70's answer is good - just adding an alternative solution here. I ran into this problem in a Web API project where I wasn't calling SerializeObject but allowing the serialization to happen automagically.
This custom JsonConverter based on Brian Rogers' answer to a similar question did the trick for me:
public class DeepDictionaryConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return (typeof(IDictionary).IsAssignableFrom(objectType) ||
TypeImplementsGenericInterface(objectType, typeof(IDictionary<,>)));
}
private static bool TypeImplementsGenericInterface(Type concreteType, Type interfaceType)
{
return concreteType.GetInterfaces()
.Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == interfaceType);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
Type type = value.GetType();
IEnumerable keys = (IEnumerable)type.GetProperty("Keys").GetValue(value, null);
IEnumerable values = (IEnumerable)type.GetProperty("Values").GetValue(value, null);
IEnumerator valueEnumerator = values.GetEnumerator();
writer.WriteStartArray();
foreach (object key in keys)
{
valueEnumerator.MoveNext();
writer.WriteStartArray();
serializer.Serialize(writer, key);
serializer.Serialize(writer, valueEnumerator.Current);
writer.WriteEndArray();
}
writer.WriteEndArray();
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
In my case, I was serializing a Dictionary<MyCustomType, int> property on a class where MyCustomType had properties like Name and Id. This is the result:
...
"dictionaryProp": [
[
{
"name": "MyCustomTypeInstance1.Name",
"description": null,
"id": null
},
3
],
[
{
"name": "MyCustomTypeInstance2.Name",
"description": null,
"id": null
},
2
]
]
...
Simpler, full solution, using a custom JsonConverter
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
public class CustomDictionaryConverter<TKey, TValue> : JsonConverter
{
public override bool CanConvert(Type objectType) => objectType == typeof(Dictionary<TKey, TValue>);
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
=> serializer.Serialize(writer, ((Dictionary<TKey, TValue>)value).ToList());
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
=> serializer.Deserialize<KeyValuePair<TKey, TValue>[]>(reader).ToDictionary(kv => kv.Key, kv => kv.Value);
}
Usage:
[JsonConverter(typeof(CustomDictionaryConverter<KeyType, ValueType>))]
public Dictionary<KeyType, ValueType> MyDictionary;
As your class can easily be serialized and deserialized into a plain string, this can be done with a custom Json converter while keeping the object structure of the Json.
I've written a JsonConverter for this purpose to convert any Dictionary in object style without needing to use arrays or type arguments for custom key types: Json.NET converter for custom key dictionaries in object style
The gist is going over the key-value-pairs manually and forcing serialization on the key type that originates from Json object properties. The most minimalistic working example I could produce:
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
// Aquire reflection info & get key-value-pairs:
Type type = value.GetType();
bool isStringKey = type.GetGenericArguments()[0] == typeof(string);
IEnumerable keys = (IEnumerable)type.GetProperty("Keys").GetValue(value, null);
IEnumerable values = (IEnumerable)type.GetProperty("Values").GetValue(value, null);
IEnumerator valueEnumerator = values.GetEnumerator();
// Write each key-value-pair:
StringBuilder sb = new StringBuilder();
using (StringWriter tempWriter = new StringWriter(sb))
{
writer.WriteStartObject();
foreach (object key in keys)
{
valueEnumerator.MoveNext();
// convert key, force serialization of non-string keys
string keyStr = null;
if (isStringKey)
{
// Key is not a custom type and can be used directly
keyStr = (string)key;
}
else
{
sb.Clear();
serializer.Serialize(tempWriter, key);
keyStr = sb.ToString();
// Serialization can wrap the string with literals
if (keyStr[0] == '\"' && keyStr[str.Length-1] == '\"')
keyStr = keyStr.Substring(1, keyStr.Length - 1);
// TO-DO: Validate key resolves to single string, no complex structure
}
writer.WritePropertyName(keyStr);
// default serialize value
serializer.Serialize(writer, valueEnumerator.Current);
}
writer.WriteEndObject();
}
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
// Aquire reflection info & create resulting dictionary:
Type[] dictionaryTypes = objectType.GetGenericArguments();
bool isStringKey = dictionaryTypes[0] == typeof(string);
IDictionary res = Activator.CreateInstance(objectType) as IDictionary;
// Read each key-value-pair:
object key = null;
object value = null;
while (reader.Read())
{
if (reader.TokenType == JsonToken.EndObject)
break;
if (reader.TokenType == JsonToken.PropertyName)
{
key = isStringKey ? reader.Value : serializer.Deserialize(reader, dictionaryTypes[0]);
}
else
{
value = serializer.Deserialize(reader, dictionaryTypes[1]);
res.Add(key, value);
key = null;
value = null;
}
}
return res;
}
With a converter like this, JSON objects can be used as dictionaries directly, as you'd expect it. In other words one can now do this:
{
MyDict: {
"Key1": "Value1",
"Key2": "Value2"
[...]
}
}
instead of this:
{
MyDict: [
["Key1", "Value1"],
["Key2", "Value2"]
[...]
]
}
See the repository for more details.

Deserialize deep-nested arrays

Currently, I have this json structure:
{
key1: value1,
key2:
[
{key3: value3}
{key4: value4}
]
}
and deserialize it into the types
IDictionary<string, object>
IDictionary<string, IList<Dictionary<string, object>>>
using a CustomCreationConverter:
public class NestedArrayConverter : CustomCreationConverter<IList<Dictionary<string, object>>>
{
public override IList<Dictionary<string, object>> Create(Type objectType)
{
return new List<Dictionary<string, object>>();
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.StartArray)
return base.ReadJson(reader, objectType, existingValue, serializer);
return serializer.Deserialize(reader);
}
}
Now I need to go one level deeper, e.g. plugging another array into value3. This is arbitrary, so value4 can be something different. How could this be done?
In the end I write the data to MongoDB and the driver doesn't work if the type is JArray or JObject. However I need structured data, a string is not enough.
It seems there's no built-in way of de-/serializing deep-nested json into anything other than JObject/JArray. So I use strings instead. I suppose one could write a converter to handle everything, however not needed in my case.
public class NestedArrayConverter : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
if (value.GetType() == typeof(List<string>))
{
var list = (List<string>)value;
writer.WriteStartArray();
foreach (var str in list)
writer.WriteRawValue(str);
writer.WriteEndArray();
}
else
{
writer.WriteValue(value);
}
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.StartArray)
{
reader.Read();
var value = new List<string>();
while (reader.TokenType != JsonToken.EndArray)
{
value.Add(JObject.Load(reader).ToString());
reader.Read();
}
return value;
}
return serializer.Deserialize(reader);
}
}

Categories

Resources