Json.NET: Deserializing nested dictionaries - c#

When deserializing an object to a Dictionary (JsonConvert.DeserializeObject<IDictionary<string,object>>(json)) nested objects are deserialized to JObjects. Is it possible to force nested objects to be deserialized to Dictionarys?

I found a way to convert all nested objects to Dictionary<string,object> by providing a CustomCreationConverter implementation:
class MyConverter : CustomCreationConverter<IDictionary<string, object>>
{
public override IDictionary<string, object> Create(Type objectType)
{
return new Dictionary<string, object>();
}
public override bool CanConvert(Type objectType)
{
// in addition to handling IDictionary<string, object>
// we want to handle the deserialization of dict value
// which is of type object
return objectType == typeof(object) || base.CanConvert(objectType);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.StartObject
|| reader.TokenType == JsonToken.Null)
return base.ReadJson(reader, objectType, existingValue, serializer);
// if the next token is not an object
// then fall back on standard deserializer (strings, numbers etc.)
return serializer.Deserialize(reader);
}
}
class Program
{
static void Main(string[] args)
{
var json = File.ReadAllText(#"c:\test.json");
var obj = JsonConvert.DeserializeObject<IDictionary<string, object>>(
json, new JsonConverter[] {new MyConverter()});
}
}
Documentation: CustomCreationConverter with Json.NET

I had a very similar but slightly more complex need when I ran across this Q. At first I thought maybe I could adapt the accepted answer, but that seemed a bit complicated and I ended up taking a different approach. I was attempting to put a modern JSON layer on top of a legacy C++ API. I'll spare you the details of that, and just say the requirements boil down to:
JSON objects become Dictionary<string,object>.
JSON arrays become List<object>.
JSON values become the corresponding primitive CLR values.
The objects and arrays can be infinitely nested.
I first deserialize the request string into a Newtonsoft JSON object and then call my method to convert in accordance with the above requirements:
var jsonObject = JsonConvert.DeserializeObject(requestString);
var apiRequest = ToApiRequest(jsonObject);
// call the legacy C++ API ...
Here is my method that converts to the structure the API expects:
private static object ToApiRequest(object requestObject)
{
switch (requestObject)
{
case JObject jObject: // objects become Dictionary<string,object>
return ((IEnumerable<KeyValuePair<string, JToken>>) jObject).ToDictionary(j => j.Key, j => ToApiRequest(j.Value));
case JArray jArray: // arrays become List<object>
return jArray.Select(ToApiRequest).ToList();
case JValue jValue: // values just become the value
return jValue.Value;
default: // don't know what to do here
throw new Exception($"Unsupported type: {requestObject.GetType()}");
}
}
I hope that someone can find this approach useful.

Alternative/Update:
I needed to deserialize a dictionary of dictionaries of Strings and with current Json.NET (5.0) I did not had to create a CustomConverter, I just used (in VB.Net):
JsonConvert.DeserializeObject(Of IDictionary(Of String, IDictionary(Of String, String)))(jsonString)
Or, in C#:
JsonConvert.DeserializeObject<IDictionary<String, IDictionary<String, String>>(jsonString);

I have a nested/deep structure of "unknown" dictionaries that is serialized/deserialized to/from C# objects and JSON string. .NET 5.
If I use Newtonsoft it does not work automatically.
If I use System.Text.Json it works automatically.
//does NOT work (newtonDeserialized does not have the same data in the nested Dictionaries as in object):
var newtonSerialized = Newtonsoft.Json.JsonConvert.SerializeObject(object);
var newtonDeserialized = Newtonsoft.Json.JsonConvert.DeserializeObject<WaitlistResponse>(newtonSerialized);
//Works (netDeserialized have the same data in the nested Directories as in object):
var netSerialized = System.Text.Json.JsonSerializer.Serialize(object);
var netDeserialized = System.Text.Json.JsonSerializer.Deserialize<WaitlistResponse>(netSerialized);

#AlexD's accepted solution does not work ideally if there is an array in the json. It returns a JArray of JObject instead a List<Dictionary<string, object>>
This can be solved by modifying the ReadJson() method:
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.StartObject || reader.TokenType == JsonToken.Null)
return base.ReadJson(reader, objectType, existingValue, serializer);
//if it's an array serialize it as a list of dictionaries
if(reader.TokenType == JsonToken.ArrayStart)
return serializer.Deserialize(reader, typeof(List<Dictionary<string, object>>));
// if the next token is not an object
// then fall back on standard deserializer (strings, numbers etc.)
return serializer.Deserialize(reader);
}

In my case, not everything is nested dictionary. I also have an array that is key-value of primitive types and it throws exception when the object of the array is not a dictionary.
So, based on Phillip S' answer, I came up with
public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue,
JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.StartObject || reader.TokenType == JsonToken.Null)
return base.ReadJson(reader, objectType, existingValue, serializer);
//if it's an array serialize it as a list of dictionaries
if (reader.TokenType == JsonToken.StartArray)
{
return serializer.Deserialize(reader, typeof(List<object>)); // instead of List<Dictionary<string, object>>
}
// if the next token is not an object
// then fall back on standard deserializer (strings, numbers etc.)
return serializer.Deserialize(reader);
}
Hope it helps those haven't got it working yet.

First of all you need to serialize your dictionary values.
var settings = new JsonSerializerSettings { TypeNameHandling= TypeNameHandling.All };
var serializeValues = JsonConvert.SerializeObject(nestedDictionaryValues, settings);
//After serialize using deserialize using the same TypeNameHandling.
var deserializeValues = JsonConvert.DeserializeObject(serializeValues, settings);

Related

Json.Net - deserialize json to a dictionary with special handling for a specific field

https://dotnetfiddle.net/ka6XVw - Fiddle with example type structure
Suppose I have a class that implements IDictionary<string, T>. Json.Net can deserialize such types out of the box, creating an instance of the type and using its indexer to populate the dictionary. The issue is that this class also inherits a string Error property marked with JsonProperty attribute from its base class, and I'd like this property to be populated whenever the input json contains an error field. However, when deserializing an IDictionary Json.Net considers all fields to be dictionary entries and tries to add the value with the error key to the dictionary.
What is the simplest and cleanest way to deserialize the json into a dictionary and the error field into the Error property? Please note that the class is generic, so JsonExtensionData is not an option (without casting its values to the provided type).
Sample valid dictionary json: { 'foo': '1', 'bar': '2' }
Sample error json { 'error': 'blah' }
I've derived a converter solution from this question. Basically, you attach a converter to your DictionaryResponse class, and interpret the incoming JSON yourself. I was lazy enough to use a JObject for parsing:
class DictionaryResponseConverter : JsonConverter<ResponseBase>
{
public override ResponseBase ReadJson(
JsonReader reader, Type objectType,
ResponseBase existingValue, bool hasExistingValue,
JsonSerializer serializer)
{
// find the correct T and call the internal function through reflection
// as DictionaryResponse<T> is sealed, we don't care about inheritance
return (ResponseBase)GetType()
.GetMethod(nameof(InternalReadJson),
BindingFlags.Instance | BindingFlags.NonPublic)
.MakeGenericMethod(objectType.GetGenericArguments()[0])
.Invoke(this, new object[]
{
reader,
existingValue,
hasExistingValue,
serializer
});
}
DictionaryResponse<T> InternalReadJson<T>(
JsonReader reader,
DictionaryResponse<T> existingValue, bool hasExistingValue,
JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null)
return null;
var obj = JObject.Load(reader);
var error = (string)obj["error"];
bool hadError = obj.Remove("error");
//var result = new DictionaryResponse<T>();
var result = hasExistingValue ? existingValue : new DictionaryResponse<T>();
foreach (var kvp in obj)
result[kvp.Key] = kvp.Value.ToObject<T>();
if (hadError)
result.Error = error;
return result;
}
public override void WriteJson(
JsonWriter writer, ResponseBase value, JsonSerializer serializer)
{
// don't care about serialization
throw new NotImplementedException();
}
}
[JsonConverter(typeof(DictionaryResponseConverter))]
internal sealed class DictionaryResponse<T> : ResponseBase, IDictionary<string, T>
{
...

How do I deserialize to object with json.net (instead of JObject)

Using json.net this test fails by default:
JsonConvert.DeserializeObject(
JsonConvert.SerializeObject(new object()),
typeof(object)
).ShouldBeOfType<object>(); // actual type is JObject
Is there a way to change this behavior, so it deserializes to the actual requested type?
You have a degenerate test case there. If you instruct Json.Net to deserialize into type object, you are telling it that the JSON could represent any possible object. So it will choose to use a JObject in that case, since you were not specific and a JObject can handle any JSON object. It is not expecting that you want to deserialize into a literal empty object instance, because that is not a very useful thing to do. If the JSON contained any data at all, you would not be able to access that data after the deserialization: object has no properties!
You can fix your test by creating an empty class Foo and using that in place of object:
JsonConvert.DeserializeObject(
JsonConvert.SerializeObject(new Foo()), typeof(Foo)
).ShouldBeOfType<Foo>();
If you really do need to force Json.Net to deserialize into an empty object instance whenever object is specified as the type, you can do it using a custom JsonConverter like this:
public class EmptyObjectConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(object);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JToken token = JToken.Load(reader); // consume the JSON object from the reader
return token.Type == JTokenType.Null ? null : new object();
}
public override bool CanWrite
{
get { return false; }
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
Then pass an instance of the converter to JsonConvert.DeserializeObject():
JsonConvert.DeserializeObject(
JsonConvert.SerializeObject(new object()),
typeof(object),
new EmptyObjectConverter()
).ShouldBeOfType<object>();
Fiddle: https://dotnetfiddle.net/7xZ7tm

C# Reference earlier JSON element - Newtonsoft

I'm serializing an object which has a List<X> and a Dictionary<X, Y>.
The List<X> is serializing without any issues.
To serialize the dictionary however, I've implemented a custom converter.
class XYDictionaryConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return typeof(Dictionary<X, Y>).IsAssignableFrom(objectType);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JObject jObject = JObject.Load(reader);
Dictionary<X, Y> dictionary = new Dictionary<X, Y>();
foreach (JProperty property in jObject.Properties())
{
X key = //Get Y using property.Name
if (key != null)
{
dictionary.Add(key, property.Value.ToObject<Y>());
}
}
return dictionary;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
IDictionary<X, Y> dictionary = (IDictionary<X, Y>)value;
JObject jObject = new JObject();
foreach (var kvp in dictionary)
{
jObject.Add(kvp.Key.name, kvp.Value == null ? null : JToken.FromObject(kvp.Value));
}
jObject.WriteTo(writer);
}
}
The problem is that I have no idea how to reference the List<X> within the converter. Assuming the JSON is deserialized linearly, the List<X> should have been deserialized by this point, but as the DeserializeObject() method hasn't finished yet, I don't have any reference to it.
How would I go about solving this?
My solution in the end was to change the way I stored 'X'.
Instead of storing the keys of the dictionary as keys in the json, I stored each keyvaluepair as an array item.
Each item then is: { "key": { "$ref" : 5 } "value": { "$ref" : 10 } }
For writing, this was just a matter of using JArray, JObjectand JToken.FromObject (making sure to pass in the serializer.
For reading, jObject.GetValue("Key").ToObject<X>(serializer) worked brilliantly.
Hope this helps someone.

JSON.net serializes a json array into a JArray when the destination is an object. How can I change that?

I have a single level json that I want to deserialize into a Dictionary<string,object> using Json.Net.
The dictionary's value can be a primitive, string or a (primitive\string) array.
The deserialization knows how to handle primitives and strings, but when it gets to an array of primitives it deserializes it into a JArray (instead of a primitive array).
Here's a small code example of what I mean:
string jsonStr = #"{""obj"": 7, ""arr"": ['1','2','3']}";
Dictionary<string, object> dict = JsonConvert.DeserializeObject<Dictionary<string, object>>(jsonStr);
dict["obj"].GetType(); // long
dict["arr"].GetType(); // JArray. I would like this to be string[].
I'm looking for a way I can interfere in the deserialization process and create a primitive array instead of getting stuck with a JArray.
I've tried doing it with the JsonSerializerSettings, but couldn't nail the spot.
Here is a hacky way of doing this. I create a custom JsonConverter class which accepts 2 generic arguments: T1 and T2. T1 gives the type of the array (in this case string) and T2 gives the type of the other object (in this case long). I assume that we basically want a Dictionary, but that part could definitely be improved.
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
string jsonStr = #"{""obj"": 7, ""arr"": ['1','2','3']}";
Dictionary<string, object> dict = JsonConvert.DeserializeObject<Dictionary<string, object>>(jsonStr, new SpecialConverter<string, long>());
dict["obj"].GetType(); // long
dict["arr"].GetType(); // string[].
}
class SpecialConverter<T1, T2> : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return true;
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JToken token = JToken.Load(reader);
var result = new Dictionary<string, object>();
if (result.GetType() == objectType)
{
foreach (var item in token)
{
var prop = (JProperty)item;
if (prop.Value.Type == JTokenType.Array)
{
result.Add(prop.Name, prop.Value.ToObject<T1[]>());
}
else
{
result.Add(prop.Name, prop.Value.ToObject<T2>());
}
}
return result;
}
else
{
return null;
}
}
public override bool CanWrite
{
get { return false; }
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
}
}
The reason it does not convert into an array is because you are telling JsonConvert to convert it to an Dictionary<string, object> and that is exactly what its doing based on the fact that it hasn't got a specific Object to convert to , hence its has "guess" picked JArray, If you can have multiple types for the dictionary's value, best thing would be to convert it to dynamic object. This will give you an array if the value of "arr" is array or a string if the value is a simple string.
string jsonStr = #"{""obj"": 7, ""arr"": ['1','2','3']}";
dynamic dict = JsonConvert.DeserializeObject(jsonStr);
var arr = dict.arr;
string firstelement = arr[0]; // print "1"

Json.NET custom serialization with JsonConverter - how to get the "default" behavior

I have a JsonConverter for my class DataType.
I would like to do some special handling when plain string used in Json as the value of a property of type DataType. In the case where the value is a "full" object, I would like to do the "normal" deserialization.
Here is my attempt
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.Value != null && reader.ValueType == typeof (string))
{
return someSpecialDataTypeInstance;
}
else if (reader.TokenType == JsonToken.StartObject)
{
DataType dataType = serializer.Deserialize<DataType>(reader);
return dataType;
}
else
{
throw new JsonSerializationException();
}
}
But this doesn't work, because this line:
DataType dataType = serializer.Deserialize(reader);
causes infinite recursion.
Could this be done somehow easily? (without the need to manually go property-by-property)
One easy way to do it is to allocate an instance of your class then use JsonSerializer.Populate(JsonReader, Object). This is the way it is done in the standard CustomCreationConverter<T>:
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.Value != null && reader.ValueType == typeof(string))
{
return someSpecialDataTypeInstance;
}
else if (reader.TokenType == JsonToken.StartObject)
{
existingValue = existingValue ?? serializer.ContractResolver.ResolveContract(objectType).DefaultCreator();
serializer.Populate(reader, existingValue);
return existingValue;
}
else if (reader.TokenType == JsonToken.Null)
{
return null;
}
else
{
throw new JsonSerializationException();
}
}
Limitations:
This does not handle situations when TypeNameHandling has been enabled and a "$type" property is present specifying a polymorphic subtype.
In this case you'll need to do some of the tricks use by JsonDerivedTypeConverer<T> in JsonConverter with Interface.
The type to be deserialized must have a parameterless constructor accessible to Json.NET. If not, and existingValue is null, it will be necessary to construct it manually, via new DataType(arg1, arg2, ...).
Reference preservation via PreserveReferencesHandling is not supported.
For one approach to handle this situation see How can I choose what type to deserialize at runtime based on the structure of the json?.
Sample fiddle.

Categories