I have a json-object in C# (represented as a Newtonsoft.Json.Linq.JObject object) and I need to flatten it to a dictionary. Let me show you an example of what I mean:
{
"name": "test",
"father": {
"name": "test2"
"age": 13,
"dog": {
"color": "brown"
}
}
}
This should yield a dictionary with the following key-value-pairs:
["name"] == "test",
["father.name"] == "test2",
["father.age"] == 13,
["father.dog.color"] == "brown"
How can I do this?
JObject jsonObject=JObject.Parse(theJsonString);
IEnumerable<JToken> jTokens = jsonObject.Descendants().Where(p => !p.HasValues);
Dictionary<string, string> results = jTokens.Aggregate(new Dictionary<string, string>(), (properties, jToken) =>
{
properties.Add(jToken.Path, jToken.ToString());
return properties;
});
I had the same requirement of flattening a nested json structure to a dictionary object. Found the solution here.
As of .NET Core 3.0 JsonDocument is a way (Json.NET is not needed). I'm sure this will get easier.
using System.Linq;
using System.Text.Json;
(...)
public static Dictionary<string, JsonElement> GetFlat(string json)
{
IEnumerable<(string Path, JsonProperty P)> GetLeaves(string path, JsonProperty p)
=> p.Value.ValueKind != JsonValueKind.Object
? new[] { (Path: path == null ? p.Name : path + "." + p.Name, p) }
: p.Value.EnumerateObject() .SelectMany(child => GetLeaves(path == null ? p.Name : path + "." + p.Name, child));
using (JsonDocument document = JsonDocument.Parse(json)) // Optional JsonDocumentOptions options
return document.RootElement.EnumerateObject()
.SelectMany(p => GetLeaves(null, p))
.ToDictionary(k => k.Path, v => v.P.Value.Clone()); //Clone so that we can use the values outside of using
}
A more expressive version is shown below.
Test
using System.Linq;
using System.Text.Json;
(...)
var json = #"{
""name"": ""test"",
""father"": {
""name"": ""test2"",
""age"": 13,
""dog"": {
""color"": ""brown""
}
}
}";
var d = GetFlat(json);
var options2 = new JsonSerializerOptions { WriteIndented = true };
Console.WriteLine(JsonSerializer.Serialize(d, options2));
Output
{
"name": "test",
"father.name": "test2",
"father.age": 13,
"father.dog.color": "brown"
}
More expressive version
using System.Linq;
using System.Text.Json;
(...)
static Dictionary<string, JsonElement> GetFlat(string json)
{
using (JsonDocument document = JsonDocument.Parse(json))
{
return document.RootElement.EnumerateObject()
.SelectMany(p => GetLeaves(null, p))
.ToDictionary(k => k.Path, v => v.P.Value.Clone()); //Clone so that we can use the values outside of using
}
}
static IEnumerable<(string Path, JsonProperty P)> GetLeaves(string path, JsonProperty p)
{
path = (path == null) ? p.Name : path + "." + p.Name;
if (p.Value.ValueKind != JsonValueKind.Object)
yield return (Path: path, P: p);
else
foreach (JsonProperty child in p.Value.EnumerateObject())
foreach (var leaf in GetLeaves(path, child))
yield return leaf;
}
I actually had the same problem earlier today couldn't find this question on SO at first, and ended up writing my own extension method to return the JValue objects containing the leaf node values of the JSON blob. It's similar to the accepted answer, except for some improvements:
It handles any JSON you give it (arrays, properties, etc) instead of just a JSON object.
Less memory usage
No calls to .Count() on descendants you ultimately don't need
Depending on your use case, those may or may not be relevant, but they are for my case. I wrote about learning to flatten the JSON.NET objects on my blog. Here is the extension method I wrote:
public static class JExtensions
{
public static IEnumerable<JValue> GetLeafValues(this JToken jToken)
{
if (jToken is JValue jValue)
{
yield return jValue;
}
else if (jToken is JArray jArray)
{
foreach (var result in GetLeafValuesFromJArray(jArray))
{
yield return result;
}
}
else if (jToken is JProperty jProperty)
{
foreach (var result in GetLeafValuesFromJProperty(jProperty))
{
yield return result;
}
}
else if (jToken is JObject jObject)
{
foreach (var result in GetLeafValuesFromJObject(jObject))
{
yield return result;
}
}
}
#region Private helpers
static IEnumerable<JValue> GetLeafValuesFromJArray(JArray jArray)
{
for (var i = 0; i < jArray.Count; i++)
{
foreach (var result in GetLeafValues(jArray[i]))
{
yield return result;
}
}
}
static IEnumerable<JValue> GetLeafValuesFromJProperty(JProperty jProperty)
{
foreach (var result in GetLeafValues(jProperty.Value))
{
yield return result;
}
}
static IEnumerable<JValue> GetLeafValuesFromJObject(JObject jObject)
{
foreach (var jToken in jObject.Children())
{
foreach (var result in GetLeafValues(jToken))
{
yield return result;
}
}
}
#endregion
}
Then in my calling code, I just extract the Path and Value properties from the JValue objects returned:
var jToken = JToken.Parse("blah blah json here");
foreach (var jValue in jToken.GetLeafValues())
{
Console.WriteLine("{0} = {1}", jValue.Path, jValue.Value);
}
You can use https://github.com/jsonfx/jsonfx to deserialize json into a dynamic object. Then use the ExpandoObject to get what you want.
public Class1()
{
string json = #"{
""name"": ""test"",
""father"": {
""name"": ""test2"",
""age"": 13,
""dog"": {
""color"": ""brown""
}
}
}";
var reader = new JsonFx.Json.JsonReader();
dynamic output = reader.Read(json);
Dictionary<string, object> dict = new Dictionary<string, object>();
GenerateDictionary((System.Dynamic.ExpandoObject) output, dict, "");
}
private void GenerateDictionary(System.Dynamic.ExpandoObject output, Dictionary<string, object> dict, string parent)
{
foreach (var v in output)
{
string key = parent + v.Key;
object o = v.Value;
if (o.GetType() == typeof(System.Dynamic.ExpandoObject))
{
GenerateDictionary((System.Dynamic.ExpandoObject)o, dict, key + ".");
}
else
{
if (!dict.ContainsKey(key))
{
dict.Add(key, o);
}
}
}
}
You could use the JSONPath $..* to get all members of the JSON structure and filter out the ones with no children to skip the container properties.
e.g.
var schemaObject = JObject.Parse(schema);
var values = schemaObject
.SelectTokens("$..*")
.Where(t => !t.HasValues)
.ToDictionary(t => t.Path, t => t.ToString());
Based on the code that was provided by tymtam, but also supporting arrays:
public static IEnumerable<KeyValuePair<string, string>> Flatten<T>(this T data, string seperator = ":") where T : class
{
var json = JsonSerializer.Serialize(data);
string GetArrayPath(string path, string name, int idx) =>
path == null ? $"{name}{seperator}{idx}" : $"{path}{seperator}{name}{seperator}{idx}";
IEnumerable<(string Path, JsonElement Element)> GetLeaves(string path, string name, JsonElement element) => element.ValueKind switch
{
JsonValueKind.Object => element.EnumerateObject()
.SelectMany(child => GetLeaves(path == null ? name : $"{path}{seperator}{name}", child.Name, child.Value)),
JsonValueKind.Array => element.EnumerateArray()
.SelectMany((child, idx) => child.ValueKind == JsonValueKind.Object
? child.EnumerateObject().SelectMany(child => GetLeaves(GetArrayPath(path, name, idx), child.Name, child.Value))
: new[] { (Path: GetArrayPath(path, name, idx), child) }
),
_ => new[] { (Path: path == null ? name : $"{path}{seperator}{name}", element) },
};
using JsonDocument document = JsonDocument.Parse(json); // Optional JsonDocumentOptions options
return document.RootElement.EnumerateObject()
.SelectMany(p => GetLeaves(null, p.Name, p.Value))
.ToDictionary(k => k.Path, v => v.Element.Clone().ToString()); //Clone so that we can use the values outside of using
}
Related
from this Answer I learned how to flatten a JSON object in c#.
from JSON String:
{"menu": {
"id": "file",
"value": "File",
"popup": {
"menuitem": [
{"value": "New", "onclick": "CreateNewDoc()"},
{"value": "Open", "onclick": "OpenDoc()"},
{"value": "Close", "onclick": "CloseDoc()"}
]
}
}}
To:
The following are lines of strings, not an object
menu.id:file
menu.value:File
menu.popup.menuitem[0].value:New
menu.popup.menuitem[0].onclick:CreateNewDoc()
menu.popup.menuitem[1].value:Open
menu.popup.menuitem[1].onclick:OpenDoc()
menu.popup.menuitem[2].value:Close
menu.popup.menuitem[2].onclick:CloseDoc()
Now, i want to reverse the process.
I can found implementations from this question but it is in JavaScript.
How do I unflatten (return structured JSON from lines) it in C# with json.net?
I managed to solve it out.
Below is my code combined with Sarath Rachuri's flattening code.
I did not test it in too many cases, so it could be buggy.
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
namespace JSONHelper
{
class JSONFlattener
{
private enum JSONType{
OBJECT, ARRAY
}
public static Dictionary<string, string> Flatten(JObject jsonObject)
{
IEnumerable<JToken> jTokens = jsonObject.Descendants().Where(p => p.Count() == 0);
Dictionary<string, string> results = jTokens.Aggregate(new Dictionary<string, string>(), (properties, jToken) =>
{
properties.Add(jToken.Path, jToken.ToString());
return properties;
});
return results;
}
public static JObject Unflatten(IDictionary<string, string> keyValues)
{
JContainer result = null;
JsonMergeSettings setting = new JsonMergeSettings();
setting.MergeArrayHandling = MergeArrayHandling.Merge;
foreach (var pathValue in keyValues)
{
if (result == null)
{
result = UnflatenSingle(pathValue);
}
else
{
result.Merge(UnflatenSingle(pathValue), setting);
}
}
return result as JObject;
}
private static JContainer UnflatenSingle(KeyValuePair<string, string> keyValue)
{
string path = keyValue.Key;
string value = keyValue.Value;
var pathSegments = SplitPath(path);
JContainer lastItem = null;
//build from leaf to root
foreach (var pathSegment in pathSegments.Reverse())
{
var type = GetJSONType(pathSegment);
switch (type)
{
case JSONType.OBJECT:
var obj = new JObject();
if (null == lastItem)
{
obj.Add(pathSegment,value);
}
else
{
obj.Add(pathSegment,lastItem);
}
lastItem = obj;
break;
case JSONType.ARRAY:
var array = new JArray();
int index = GetArrayIndex(pathSegment);
array = FillEmpty(array, index);
if (lastItem == null)
{
array[index] = value;
}
else
{
array[index] = lastItem;
}
lastItem = array;
break;
}
}
return lastItem;
}
public static IList<string> SplitPath(string path){
IList<string> result = new List<string>();
Regex reg = new Regex(#"(?!\.)([^. ^\[\]]+)|(?!\[)(\d+)(?=\])");
foreach (Match match in reg.Matches(path))
{
result.Add(match.Value);
}
return result;
}
private static JArray FillEmpty(JArray array, int index)
{
for (int i = 0; i <= index; i++)
{
array.Add(null);
}
return array;
}
private static JSONType GetJSONType(string pathSegment)
{
int x;
return int.TryParse(pathSegment, out x) ? JSONType.ARRAY : JSONType.OBJECT;
}
private static int GetArrayIndex(string pathSegment)
{
int result;
if (int.TryParse(pathSegment, out result))
{
return result;
}
throw new Exception("Unable to parse array index: " + pathSegment);
}
}
}
Purely System.Text.Json solution for unflatteing JSON. Requires .Net 6.
private static JsonNode Unflatten(Dictionary<string, JsonValue> source)
{
var regex = new System.Text.RegularExpressions.Regex(#"(?!\.)([^. ^\[\]]+)|(?!\[)(\d+)(?=\])");
JsonNode node = JsonNode.Parse("{}");
foreach (var keyValue in source)
{
var pathSegments = regex.Matches(keyValue.Key).Select(m => m.Value).ToArray();
for (int i = 0; i < pathSegments.Length; i++)
{
var currentSegmentType = GetSegmentKind(pathSegments[i]);
if (currentSegmentType == JsonValueKind.Object)
{
if (node[pathSegments[i]] == null)
{
if (pathSegments[i] == pathSegments[pathSegments.Length - 1])
{
node[pathSegments[i]] = keyValue.Value;
node = node.Root;
}
else
{
var nextSegmentType = GetSegmentKind(pathSegments[i + 1]);
if (nextSegmentType == JsonValueKind.Object)
{
node[pathSegments[i]] = JsonNode.Parse("{}");
}
else
{
node[pathSegments[i]] = JsonNode.Parse("[]");
}
node = node[pathSegments[i]];
}
}
else
{
node = node[pathSegments[i]];
}
}
else
{
if (!int.TryParse(pathSegments[i], out int index))
{
throw new Exception("Cannot parse index");
}
while (node.AsArray().Count - 1 < index)
{
node.AsArray().Add(null);
}
if (i == pathSegments.Length - 1)
{
node[index] = keyValue.Value;
node = node.Root;
}
else
{
if (node[index] == null)
{
var nextSegmentType = GetSegmentKind(pathSegments[i + 1]);
if (nextSegmentType == JsonValueKind.Object)
{
node[index] = JsonNode.Parse("{}");
}
else
{
node[index] = JsonNode.Parse("[]");
}
}
node = node[index];
}
}
}
}
return node;
}
private static JsonValueKind GetSegmentKind(string pathSegment) =>
int.TryParse(pathSegment, out _) ? JsonValueKind.Array : JsonValueKind.Object;
To flatten a JSON object:
arrayJSON.stringify()
The following javascript question is the same problem i'm attempting to solve but in c#
How can I merge 2 dot notation strings to a GraphQL query string
Expected structure is
{
"Case": {
"Owner": {
"Name": null,
"ProfilePic": null
},
"CaseNo": null,
"FieldOfLaw":{
"Name": null
},
"CaseType": {
"Name": null
},
"CaseSubType": {
"Name": null
},
},
"Client":{
"Policy":{
"PolicyNo": null
}
}
}
and my current output is
{
"Case": {
"Owner": {
"Name": null,
"ProfilePic": null
},
"CaseNo": null,
"FieldOfLaw": null,
"CaseType": null,
"CaseSubType": null
}
}
Below is my attempt using ExpandoObjects to try dynamically generate the objects needed. Any advice or pointers in the right direction would be appreciated.
public static void Main(string[] args)
{
var FieldList = new List<string>
{
"Case.Owner.Name",
"Case.Owner.ProfilePic",
"Case.CaseNo",
"Case.FieldOfLaw.Name",
"Case.CaseType.Name",
"Case.CaseSubType.Name",
"Client.Policy.PolicyNo",
};
Parser graphQL = new Parser();
var result = graphQL.Parse(FieldList);
Console.Write(result);
}
Below is the actual parse method, so i'm running an aggregation function over each element to create and return the expando objects into the initial holder. I traverse each split string recursively and exit the recursion once there are no more items left in the split list.
public class Parser
{
public string Parse(List<string> fieldList)
{
// List<ExpandoObject> queryHolder = new List<ExpandoObject>();
ExpandoObject intialSeed = new ExpandoObject();
fieldList.Aggregate(intialSeed, (holder, field) =>
{
holder = ParseToObject(holder, field.Split('.').ToList());
return holder;
});
return JsonConvert.SerializeObject(intialSeed);
}
public ExpandoObject ParseToObject(ExpandoObject holder, List<string> fieldSplit, string previousKey = null)
{
if (fieldSplit.Any())
{
var item = fieldSplit.Shift();
if (item == null)
return holder;
// If the current item doesn't exists in the dictionary
if (!((IDictionary<string, object>)holder).ContainsKey(item))
{
if (((IDictionary<string, object>)holder).Keys.Count() == 0)
holder.TryAdd(item, null);
else
_ = ((IDictionary<string, object>)holder).GetItemByKeyRecursively(previousKey, item);
}
previousKey = item;
ParseToObject(holder, fieldSplit, previousKey);
}
return holder;
}
}
Here are my two extensions methods, i'm having an issue with the GetItemByKeyRecursively when it goes into the 3rd level in it's recursion so example.
I'm adding FieldOfLaw it adds the property to the Case expandoObject but doesn't know how to get back to the leaf containing Owner, CaseNo etc.
public static class CollectionExtensions
{
public static T Shift<T>(this IList<T> list)
{
var shiftedElement = list.FirstOrDefault();
list.RemoveAt(0);
return shiftedElement;
}
public static IDictionary<string, object> GetItemByKeyRecursively(this IDictionary<string, object> dictionary, string parentKey, string keyToCreate)
{
foreach (string key in dictionary.Keys)
{
var leaf = dictionary[key];
if (key == parentKey)
{
var #value = dictionary[key];
if (#value is ExpandoObject)
{
(#value as ExpandoObject).TryAdd(keyToCreate, null);
}
else if (#value == null)
{
var item = new ExpandoObject();
item.TryAdd(keyToCreate, null);
dictionary[key] = item;
}
return dictionary;
}
if (leaf == null)
continue;
return GetItemByKeyRecursively((IDictionary<string, object>)leaf, parentKey, keyToCreate);
}
return null;
}
}
Nothing you can't accomplish mostly declaratively.
public string Parse(List<string> fieldList)
{
var fieldPaths = fieldList.Select(x => x.Split('.').ToList());
var groups = fieldPaths.GroupBy(x => x.First(), x => x.Skip(1));
return ParseGroups(groups, 1);
}
private string ParseGroups(IEnumerable<IGrouping<string, IEnumerable<string>>> groups, int level)
{
string indent = new string('\t', level - 1);
var groupResults = groups.Select(g =>
!g.First().Any() ?
$"\t{indent}{g.Key}: null" :
$"\t{indent}{g.Key}: " + string.Join(", \n",
ParseGroups(g.GroupBy(x => x.First(), x => x.Skip(1)), level + 1))
);
return indent + "{\n" + string.Join(", \n", groupResults) + "\n" + indent + "}";
}
See the complete sample code here: https://dotnetfiddle.net/RLygjt
I have the below dictionary, and I need to convert the complex key values into proper JSON.
static void Main(string[] args)
{
Dictionary<string, object> collectionProp = new Dictionary<string, object>();
Dictionary<string, object> prop = new Dictionary<string, object>();
prop.Add("content", "button");
prop.Add("page.count", "10");
prop.Add("columns.0.textAlign", "center");
prop.Add("columns.1.textAlign", "left");
var result = new Dictionary<string, object>();
foreach (var pair in prop)
{
var key = pair.Key;
var parts = key.Split('.');
var currentObj = result;
for (int i = 0; i < parts.Length - 1; i++)
{
var property = parts[i];
if (!currentObj.Keys.Contains(property))
currentObj[property] = new Dictionary<string, object>();
currentObj = (Dictionary<string, object>)currentObj[property];
}
currentObj[parts[parts.Length - 1]] = pair.Value;
}
Console.WriteLine(JsonConvert.SerializeObject(result, Formatting.Indented));
Console.ReadLine();
}
And getting the following result.
{
"content": "button",
"page": {
"count": "10"
},
"columns": {
"0": {
"textAlign": "center"
},
"1": {
"textAlign": "left"
}
}
}
Expecting the columns should be grouped as JSON array. How to achieve this.?
The desired output:
{
"content": "button",
"page": {
"count": "10"
},
"columns": [
{
"textAlign": "center"
},
{
"textAlign": "left"
}
]
}
JSON.NET will serialize dictionaries as JSON objects by default even though its keys are convertible to an integer. Instead of building a dictionary from the source you could build JObject hierarchy. These helper methods recognize array indices indices in your path to automatically build JArray container around them:
public static class JsonExtensions
{
public static void SetValue(this JContainer container, string path, object value)
{
JToken token = container;
var keys = path.Split('.');
foreach (var key in keys)
{
int index;
if (int.TryParse(key, out index))
{
var jArray = token as JArray;
if (jArray == null)
{
jArray = new JArray();
token.Replace(jArray);
token = jArray;
}
while (index >= jArray.Count)
{
jArray.Add(JValue.CreateNull());
}
token = jArray[index];
}
else
{
var jObject = token as JObject;
if (jObject == null)
{
jObject = new JObject();
token.Replace(jObject);
token = jObject;
}
token = token[key] ?? (token[key] = JValue.CreateNull());
}
}
token.Replace(new JValue(value));
}
public static void SetValues(this JContainer container, IEnumerable<KeyValuePair<string, object>> pairs)
{
foreach (var pair in pairs)
{
container.SetValue(pair.Key, pair.Value);
}
}
}
And here's how you get the results you expect:
var jObject = new JObject();
jObject.SetValues(new Dictionary<string, object>
{
{ "content", "button" },
{ "page.count", "10" },
{ "columns.0.textAlign", "center" },
{ "columns.1.textAlign", "left" }
});
Console.WriteLine(jObject.ToString(Formatting.Indented));
Note that the code sample I provided is no way near efficient and should be used just as an inspiration to achieve the result you require.
Also note that in some cases the order of values to build the JObject matters, but enumerating items from dictionary is non-deterministic. Therefore you might consider a better data structure for the source that guarantees order of key-value pairs in it such as an array.
I am trying to fetch values from JSON below on the basis of their nodes in key-value pairs. I need to store values of JSON fields in a list or collection. I tried with the code below, but it did not give the desired output. How can I achieve this?
As of now, I'm doing this:
HttpPostedFileBase JSfile = Request.Files["UploadedJSFile"];
if ((JSfile != null) && (JSfile.ContentLength > 0) && !string.IsNullOrEmpty(JSfile.FileName))
{
BinaryReader b = new BinaryReader(JSfile.InputStream);
byte[] binData = b.ReadBytes(JSfile.ContentLength);
string result = System.Text.Encoding.UTF8.GetString(binData);
JArray jsonVal = JArray.Parse(result) as JArray;
foreach (JObject content in jsonVal.Children<JObject>())
{
foreach (JProperty prop in content.Properties())
{
filters.Add(prop.Name, prop.Value.ToString());
}
}
}
But it's not giving the desired output.
I want all values with their nodes like: herobanner.landing.copies.watchvideo
Here is my JSON:
[{
"country": "en-in",
"heroBanner": {
"landing": {
"copies": {
"watchVideo": "watch the video",
"scrollDown": [
"READ INSPIRING STORIES",
""
],
"banner": [
"make your",
"first move this",
"valentine's Day"
]
},
"background": {
"desktop": "assets/images/millions-hero-1.jpg"
},
"foreground": {
"desktop": ""
},
"video": {
"youtubeId": "buwcDIcFR8I"
}
}
}
}]
You could make a recursive extension method to flatten the JToken hierarchy to a Dictionary:
public static class JsonExtensions
{
public static Dictionary<string, object> Flatten(this JToken token)
{
var dict = new Dictionary<string, object>();
Flatten(token, dict);
return dict;
}
private static void Flatten(JToken token, Dictionary<string, object> dict)
{
switch (token.Type)
{
case JTokenType.Object:
foreach (JProperty prop in token.Children<JProperty>())
{
Flatten(prop.Value, dict);
}
break;
case JTokenType.Array:
foreach (JToken child in token.Children())
{
Flatten(child, dict);
}
break;
default:
dict.Add(token.Path, ((JValue)token).Value);
break;
}
}
}
Then use it like this, assuming jsonVal is a JToken of some kind (e.g. JArray or JObject):
var dict = jsonVal.Flatten();
foreach (var kvp in dict)
{
Console.WriteLine(kvp.Key + ": " + kvp.Value);
}
Fiddle: https://dotnetfiddle.net/sVrM2e
I have a web API method that accepts an arbitrary json payload into a JObject property. As such I don't know what's coming but I still need to translate it to .NET types. I would like to have a Dictionary<string,object> so that I can deal with it any way I want to.
I have searched a lot, but couldn't find anything and ended up starting a messy method to do this conversion, key by key, value by value. Is there an easy way to do it?
Input ->
JObject person = new JObject(
new JProperty("Name", "John Smith"),
new JProperty("BirthDate", new DateTime(1983, 3, 20)),
new JProperty("Hobbies", new JArray("Play football", "Programming")),
new JProperty("Extra", new JObject(
new JProperty("Foo", 1),
new JProperty("Bar", new JArray(1, 2, 3))
)
)
Thanks!
If you have JObject objects, the following might work:
JObject person;
var values = person.ToObject<Dictionary<string, object>>();
If you do not have a JObject you can create one with the Newtonsoft.Json.Linq extension method:
using Newtonsoft.Json.Linq;
var values = JObject.FromObject(person).ToObject<Dictionary<string, object>>();
Otherwise, this answer might point you in the right direction, as it deserializes a JSON string to a Dictionary.
var values = JsonConvert.DeserializeObject<Dictionary<string, object>>(json);
I ended up using a mix of both answers as none really nailed it.
ToObject() can do the first level of properties in a JSON object, but nested objects won't be converted to Dictionary().
There's also no need to do everything manually as ToObject() is pretty good with first level properties.
Here is the code:
public static class JObjectExtensions
{
public static IDictionary<string, object> ToDictionary(this JObject #object)
{
var result = #object.ToObject<Dictionary<string, object>>();
var JObjectKeys = (from r in result
let key = r.Key
let value = r.Value
where value.GetType() == typeof(JObject)
select key).ToList();
var JArrayKeys = (from r in result
let key = r.Key
let value = r.Value
where value.GetType() == typeof(JArray)
select key).ToList();
JArrayKeys.ForEach(key => result[key] = ((JArray)result[key]).Values().Select(x => ((JValue)x).Value).ToArray());
JObjectKeys.ForEach(key => result[key] = ToDictionary(result[key] as JObject));
return result;
}
}
It might have edge cases where it won't work and the performance is not the strongest quality of it.
Thanks guys!
I've modified the code to recurse JArrays an JObjects nested in JArrays/JObjects, which the accepted answer does not, as pointed out by #Nawaz.
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json.Linq;
public static class JsonConversionExtensions
{
public static IDictionary<string, object> ToDictionary(this JObject json)
{
var propertyValuePairs = json.ToObject<Dictionary<string, object>>();
ProcessJObjectProperties(propertyValuePairs);
ProcessJArrayProperties(propertyValuePairs);
return propertyValuePairs;
}
private static void ProcessJObjectProperties(IDictionary<string, object> propertyValuePairs)
{
var objectPropertyNames = (from property in propertyValuePairs
let propertyName = property.Key
let value = property.Value
where value is JObject
select propertyName).ToList();
objectPropertyNames.ForEach(propertyName => propertyValuePairs[propertyName] = ToDictionary((JObject) propertyValuePairs[propertyName]));
}
private static void ProcessJArrayProperties(IDictionary<string, object> propertyValuePairs)
{
var arrayPropertyNames = (from property in propertyValuePairs
let propertyName = property.Key
let value = property.Value
where value is JArray
select propertyName).ToList();
arrayPropertyNames.ForEach(propertyName => propertyValuePairs[propertyName] = ToArray((JArray) propertyValuePairs[propertyName]));
}
public static object[] ToArray(this JArray array)
{
return array.ToObject<object[]>().Select(ProcessArrayEntry).ToArray();
}
private static object ProcessArrayEntry(object value)
{
if (value is JObject)
{
return ToDictionary((JObject) value);
}
if (value is JArray)
{
return ToArray((JArray) value);
}
return value;
}
}
Here is a simpler version:
public static object ToCollections(object o)
{
var jo = o as JObject;
if (jo != null) return jo.ToObject<IDictionary<string, object>>().ToDictionary(k => k.Key, v => ToCollections(v.Value));
var ja = o as JArray;
if (ja != null) return ja.ToObject<List<object>>().Select(ToCollections).ToList();
return o;
}
If using C# 7 we can use pattern matching where it would look like this:
public static object ToCollections(object o)
{
if (o is JObject jo) return jo.ToObject<IDictionary<string, object>>().ToDictionary(k => k.Key, v => ToCollections(v.Value));
if (o is JArray ja) return ja.ToObject<List<object>>().Select(ToCollections).ToList();
return o;
}
Sounds like a good use case for extension methods - I had something lying around that was pretty straightforward to convert to Json.NET (Thanks NuGet!):
Of course, this is quickly hacked together - you'd want to clean it up, etc.
public static class JTokenExt
{
public static Dictionary<string, object>
Bagify(this JToken obj, string name = null)
{
name = name ?? "obj";
if(obj is JObject)
{
var asBag =
from prop in (obj as JObject).Properties()
let propName = prop.Name
let propValue = prop.Value is JValue
? new Dictionary<string,object>()
{
{prop.Name, prop.Value}
}
: prop.Value.Bagify(prop.Name)
select new KeyValuePair<string, object>(propName, propValue);
return asBag.ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
}
if(obj is JArray)
{
var vals = (obj as JArray).Values();
var alldicts = vals
.SelectMany(val => val.Bagify(name))
.Select(x => x.Value)
.ToArray();
return new Dictionary<string,object>()
{
{name, (object)alldicts}
};
}
if(obj is JValue)
{
return new Dictionary<string,object>()
{
{name, (obj as JValue)}
};
}
return new Dictionary<string,object>()
{
{name, null}
};
}
}