Introduction
Microsoft offers JavascriptSerializer
as part of their support for JSON serialization which is a high level serializer that implements IDictionary<T,T>
. There are so many developers who don’t use JavascriptSerializer
for JSON Deserialization due to the fact that it is very difficult to handle the output. Architecturally, in deserialization, the returned value is abstracted as Dictionary<T,T>
. Developers can deal with the result if the JSON object is not deeply nested, but when the JSON object is deeply nested, then developers try to spin out or find other serialization/deserialization processors for simplicity.
Deeply Nested JSON Problem in C#
For illustration, the following simple JSON Object can be turned in Dictionary
in a straight-forward manner:
{
"Key1" : "Value1" ,
"Key2" : "Value2"
}
The deserialized abstraction would look like this:
key value
key1 value1
key2 value2
Now let’s consider an extended scenario where the JSON Object is very nested such as the following object:
{
{
"Key1" :
{
"Sub-Key1" : "Sub-Value1" ,
"Sub-Key2" : "Sub-Value2" ,
"Sub-Key3" : "Sub-Value3" ,
}
"Key2" :
{
"Sub-Key1" :[ { "Sub-Sub-Key1" : "Val1" } , { "Sub-Sub-Key2" : "Val2" } ] ,
"Sub-Key2" :[ { "Sub-Sub-Key1" : "Val1" } , { "Sub-Sub-Key2" : "Val2" } ]
}
}
The internal interpretation of any JSON Serialization Processor for this particular object would have to be a dictionary
that contains regular [Key,Value
] Pairs, But in certain buckets, the value in each [Key,Value
] is either a sub-dictionary or a collection of sub-dictionaries. From the view of point of any JSON Serialization Processor is as follows:
Key Value
Key1 Dictionary
Key Value
Sub-Key1 SubValue1
Sub-Key2 SubValue2
Sub-Key3 SubValue3
Key2 Dictionary
Key Value
Sub-Key1 Collection
3 Dictionaries
Sub-Key2 Collection
3 Dictionaries
Many solutions have been proposed to solve this problem with nested JSON, the most famous one suggests the use of the new feature in C# 4.0 that is ExpandoObject
with Extension Method to the returned Base Dictionary, From my personal benchmarking, I found that generating ~100K ExpandoObjects requires between 50-100 MB of RAM for each request. Therefore, I propose an alternative for the use of ExpandoObject
to hold inner dictionaries by making use of Method Recursion.
Using the Code
The first method is responsible for passing JSON Object as string
and returns fully deserialized object in dictionary
. Please note that this is an extension method to the datatype String
. The second method is responsible for resolving the entire Object
in the same hierarchy that is supposed to be represented as:
public static Dictionary<string, object> deserializeJson(this string json)
{
JavaScriptSerializer serializer = new JavaScriptSerializer();
Dictionary<string, object> dictionary =
serializer.Deserialize<Dictionary<string, object>>(json);
return dictionary;
}
static void resolveEntry(Dictionary<string, object> dic, string SupKey)
{
foreach (KeyValuePair<string, object> entry in dic)
{
if (entry.Value is Dictionary<string, object>)
resolveEntry((Dictionary<string, object>)entry.Value, entry.Key);
else
if (entry.Value is ICollection)
{
foreach (var item in (ICollection)entry.Value)
{
if (item is Dictionary<string, object>)
resolveEntry((Dictionary<string, object>)item, SupKey + " : " + entry.Key);
else
Console.WriteLine(item.ToString());
}
}
else
Console.WriteLine(SupKey + " : " +
entry.Key.ToString() + "--->" + entry.Value.ToString());
}
}
Dictionary<string, object> dic = plsfobject.deserializeJson();
resolveEntry(dic, "Base");
Console.ReadKey();
Points of Interest
The result output we print as with the following picture. However, you can extend the idea for passing each flat object to a database, static file, ... etc. also you can benchmark the code and compare it with other approaches and let me know.
Recursion VS ExpandoObject
The following solution is supposed to achieve the same functionality but with bad performance in terms of speed and space. Please notice that both of these methods are extension methods and must live in static class. Also you can call them from the caller method as an extenstion to DataType String
. The main difference is that in the first approach, you will need to initialize one ExpandoObject
for holding the entire dictionary, whereas in the second approach the code produces linear Expando initializations with few level depth of JSON Nesting, and it can produce Exponential Expando initializations with very deep nested JSON objects.
public static ExpandoObject ToExpando(this string json)
{
JavaScriptSerializer serializer = new JavaScriptSerializer();
IDictionary<string, object> dictionary =
serializer.Deserialize<IDictionary<string, object>>(json);
return dictionary.Expando();
}
public static ExpandoObject Expando(this IDictionary<string, object> dictionary)
{
ExpandoObject expandoObject = new ExpandoObject();
IDictionary<string, object> objects = expandoObject;
foreach (var item in dictionary)
{
bool processed = false;
if (item.Value is IDictionary<string, object>)
{
objects.Add(item.Key, Expando((IDictionary<string, object>)item.Value));
processed = true;
}
else if (item.Value is ICollection)
{
List<object> itemList = new List<object>();
foreach (var item2 in (ICollection)item.Value)
if (item2 is IDictionary<string, object>)
itemList.Add(Expando((IDictionary<string, object>)item2));
else
itemList.Add(Expando(new Dictionary<string,
object> { { "Unknown", item2 } }));
if (itemList.Count > 0)
{
objects.Add(item.Key, itemList);
processed = true;
}
}
if (!processed)
objects.Add(item);
}
return expandoObject;
}
string myJson = "Some Object String";
ExpandoObject myExObj = myJson.Expando();