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.
Related
I have a json configuration file where I would like to store a json object that could hold several unknown fields.
I can deserialize such an object using Newtonsoft by deriving from this base class:
public class ComplexJsonObject
{
[JsonExtensionData]
private IDictionary<string, JToken> _additionalData_newtonsoft;
}
Unfortunately it seems that the config file appsettings.development.json just says I have an
empty object. Even though there is something configured.
I assumed this was because the system used System.Text.Json.
So I tried that as well:
public class ComplexJsonObject
{
[JsonExtensionData]
private IDictionary<string, JToken> _additionalData_newtonsoft;
[System.Text.Json.Serialization.JsonExtensionData]
[Newtonsoft.Json.JsonIgnore]
public IDictionary<string, JsonElement> _additionalData_dotnet { get; set; }
}
This does not work either.
So the question: how do I tell the system to use Newtonsoft for deserializing this config file?
-- edit --
As requested, an example of the config I would like to store.
The config key would be "configuration:when" and the object I expect must have a operator, but all the other fields are dynamic.
{
"extraction": {
"when": {
"operator": "and",
"rules": [
{
"operator": "or",
"rules": [
{ "operator": "field.contains", "value": "waiting" },
{ "operator": "field.contains", "value": "approved" },
{ "operator": "field.contains", "value": "rejected" }
]
},
{ "operator": "not", "rule": { "operator": "field.contains", "value": "imported" } },
{ "operator": "not", "rule": { "operator": "field.contains", "value": "import-failed" } }
]
}
}
}
I think Métoule is correct, and this is indeed not possible. Since the config by default would mix values from other files.
If you need to convert just a specific section using newtonsoft, you can use this extension:
public static class ConfigurationBinder
{
public static void BindJsonNet<T>(this IConfiguration config, T instance) where T : class
{
string objectPath = config.AsEnumerable().Select(x => x.Key).FirstOrDefault();
var obj = BindToExpandoObject(config, objectPath);
if (obj == null)
return;
var jsonText = JsonConvert.SerializeObject(obj);
var jObj = JObject.Parse(jsonText);
if (jObj == null)
return;
var jToken = jObj.SelectToken($"*.{GetLastObjectName(objectPath)}");
if (jToken == null)
return;
jToken.Populate<T>(instance);
}
private static ExpandoObject BindToExpandoObject(IConfiguration config, string objectPath)
{
var result = new ExpandoObject();
string lastObjectPath = GetLastObjectPath(objectPath);
// retrieve all keys from your settings
var configs = config.AsEnumerable();
configs = configs
.Select(x => new KeyValuePair<string, string>(x.Key.Replace(lastObjectPath, ""), x.Value))
.ToArray();
foreach (var kvp in configs)
{
var parent = result as IDictionary<string, object>;
var path = kvp.Key.Split(':');
// create or retrieve the hierarchy (keep last path item for later)
var i = 0;
for (i = 0; i < path.Length - 1; i++)
{
if (!parent.ContainsKey(path[i]))
{
parent.Add(path[i], new ExpandoObject());
}
parent = parent[path[i]] as IDictionary<string, object>;
}
if (kvp.Value == null)
continue;
// add the value to the parent
// note: in case of an array, key will be an integer and will be dealt with later
var key = path[i];
parent.Add(key, kvp.Value);
}
// at this stage, all arrays are seen as dictionaries with integer keys
ReplaceWithArray(null, null, result);
return result;
}
private static string GetLastObjectPath(string objectPath)
{
string lastObjectPath = objectPath;
int indexLastObj;
if ((indexLastObj = objectPath.LastIndexOf(":")) != -1)
lastObjectPath = objectPath.Remove(indexLastObj);
return lastObjectPath;
}
private static string GetLastObjectName(string objectPath)
{
string lastObjectPath = objectPath;
int indexLastObj;
if ((indexLastObj = objectPath.LastIndexOf(":")) != -1)
lastObjectPath = objectPath.Substring(indexLastObj + 1);
return lastObjectPath;
}
private static void ReplaceWithArray(ExpandoObject parent, string key, ExpandoObject input)
{
if (input == null)
return;
var dict = input as IDictionary<string, object>;
var keys = dict.Keys.ToArray();
// it's an array if all keys are integers
if (keys.All(k => int.TryParse(k, out var dummy)))
{
var array = new object[keys.Length];
foreach (var kvp in dict)
{
array[int.Parse(kvp.Key)] = kvp.Value;
}
var parentDict = parent as IDictionary<string, object>;
parentDict.Remove(key);
parentDict.Add(key, array);
}
else
{
foreach (var childKey in dict.Keys.ToList())
{
ReplaceWithArray(input, childKey, dict[childKey] as ExpandoObject);
}
}
}
public static void Populate<T>(this JToken value, T target) where T : class
{
using (var sr = value.CreateReader())
{
JsonSerializer.CreateDefault().Populate(sr, target); // Uses the system default JsonSerializerSettings
}
}
}
Usage:
var obj = new SampleObject();
Configuration.GetSection("test:arr:3:sampleObj").BindJsonNet(obj);
services.AddSingleton(obj);
Reference:
https://stackoverflow.com/a/55050425/7064571
Bind netcore IConfigurationSection to a dynamic object
What you want is not possible, because that's not how the .NET configuration system works. The configuration doesn't directly parse your JSON into your data structure; instead, it creates an intermediate representation (which is a simple IDictionary<string,string>) which is then bound to your data structure.
The reason why it's not a direct mapping is because the configuration data can come from multiple sources. For example, it's possible to override the JSON configuration with values specified via the Azure portal UI. Or there might not be a JSON file at all.
That being said, it's possible to abuse the configuration system, like I explained in the following questions:
How to load polymorphic objects in appsettings.json
Bind netcore IConfigurationSection to a dynamic object
How can I sort a list of dot notated namespace strings into a nested JSON string?
something like this.
var string1 = "example.string.1";
var string2 = "example.string.2";
var string3 = "example.anotherstring.1";
sorted into this.
{
"example": {
"string": ["1", "2"],
"anotherstring": ["1"]
}
}
EDIT 1
Thanks guys! I was able to get it CLOSE to working using your answers.
with this list it works:
var strings = new List<string>
{
"example.string.1",
"example.string.2",
"example.anotherstring.1",
};
public void Example()
{
var dict = new Dictionary<string, dynamic>();
foreach (var s in strings)
{
AddPartsToDictionary(dict, s.Split('.'));
}
}
public void AddPartsToDictionary(IDictionary<string, dynamic> dict, string[] parts)
{
for (var i = 0; i < parts.Length; i++)
{
if (i < parts.Length - 2)
{
if (!dict.ContainsKey(parts[i]))
dict.Add(parts[i], new Dictionary<string, dynamic>());
dict = dict[parts[i]];
}
else if (i < parts.Length - 1)
{
if (!dict.ContainsKey(parts[i]))
{
var list = new List<string>();
dict[parts[i]] = list;
}
}
else
{
var list = dict[parts[i - 1]] as List<string>;
list.Add(parts[i]);
}
}
}
result json
{
"example": {
"string": [
"1",
"2"
],
"anotherstring": [
"1"
]
}
}
with this list it fails
var strings = new List<string>
{
"example.string.1",
"example.string.example.1",
"example.string.2",
"example.anotherstring.1",
"example.string.example.2",
"string.example.2"
};
I might have to sort the list or something, still working on it. Hope this helps with figuring it out.
Objects All the Way Down
If using objects (instead of arrays) all the time is okay, then this will work.
var strings = new List<string> {
"example.string.1",
"example.string.2",
"example.anotherstring.1",
};
var result = strings.Aggregate(new Dictionary<string, Object>(), (acc, s) =>
{
var level = acc;
foreach(var segment in s.Split('.'))
{
if (!level.ContainsKey(segment))
{
var child = new Dictionary<string, Object>();
level.Add(segment, child);
}
level = level[segment] as Dictionary<string, Object>;
}
return acc;
});
var json = JsonConvert.SerializeObject(result, Formatting.Indented);
The output has objects all the way down.
{
"example": {
"string": {
"1": {},
"2": {}
},
"anotherstring": {
"1": {}
}
}
}
Arrays at the Last Level (with a Fiddle)
If you would like arrays at the last level, then we need a precise definition of what defines the last level. For instance, does the last level always contain only integers? If so the following works.
var result = strings.Aggregate(new Dictionary<string, Object>(), (acc, s) =>
{
Dictionary<string, Object> previousLevel = null;
Dictionary<string, Object> nextLevel = acc;
string previousSegment = null;
foreach (string nextSegment in s.Split('.'))
{
if (Int16.TryParse(nextSegment, out _))
{
if (previousLevel[previousSegment] is Dictionary<string, Object>)
{
previousLevel[previousSegment] = new List<string>();
}
var list = previousLevel[previousSegment] as List<string>;
list.Add(nextSegment);
}
else
{
if (!nextLevel.ContainsKey(nextSegment))
{
var child = new Dictionary<string, Object>();
nextLevel.Add(nextSegment, child);
}
previousSegment = nextSegment;
previousLevel = nextLevel;
nextLevel = nextLevel[nextSegment] as Dictionary<string, Object>;
}
}
return acc;
});
The output has arrays at the last level.
{
"example": {
"string": [
"1",
"2"
],
"anotherstring": [
"1",
"2"
]
}
}
This array version will work only if the last level is only of integer types, which is what your original question required.
The more difficult case is to handle mixed string and integer types at the same level, which would have an input that looks something like this:
"System.1",
"System.2",
"System.Collections.1",
"System.Collections.2",
"System.Collections.Generic.1"
That requires a more complex algorithm. If that is the hidden requirement, consider asking a brand new follow-up question and linking to it in a comment to this answer.
I thought id have a quick go at this, its close but not exactly what you wanted. However i thought it might inspire you or someone else to have a try.
Disclaimer, i'm not really a Json.net aficionado and this code is
fairly well.. unusual, to say the least
Demo Here
Given a list
var list = new List<string>
{
"example.string.1",
"example.string.2",
"example.anotherstring.1",
"example.anotherstring.2",
"System",
"System.Diagnostics",
"System.Text",
"System.Collections.Generic",
"System.Linq",
"System.Diagnostics.1",
"System.Text.1",
"System.Collections.Generic.1",
"System.Linq.1",
"System.Diagnostics.2",
"System.Text.2",
"System.Collections.Generic.2",
"System.Linq.2"
};
Some Classes
public class RawValue
{
public RawValue(string key, IEnumerable<IEnumerable<string>> values)
{
Key = key;
Values = values;
}
public string Key { get; set; }
public IEnumerable<IEnumerable<string>> Values { get; set; }
}
public class NameSpace : Dictionary<string, NameSpace>
{
}
Some Helpers
private static IEnumerable<RawValue> GetRawValues(RawValue value)
{
return value.Values.Where(x => x.Any())
.GroupBy(x => x.FirstOrDefault())
.Select(x => new RawValue(x.Key, x.Select(y => y.Skip(1))));
}
private static NameSpace GroupNameSpaces(IEnumerable<RawValue> groups)
{
var result = new NameSpace();
foreach (var group in groups)
result.Add(group.Key, GroupNameSpaces(GetRawValues(group)));
return result;
}
Main Code
var groups = list.Select(x => x.Split('.'))
.GroupBy(x => x.FirstOrDefault())
.Select(x => new RawValue(x.Key, x.Select(y => y.Skip(1))));
var settings = new JsonSerializerSettings
{
NullValueHandling = NullValueHandling.Ignore
};
var result = GroupNameSpaces(groups);
var json = JsonConvert.SerializeObject(result, Formatting.Indented, settings);
Console.WriteLine(json);
Output
{
"example": {
"string": {
"1": {},
"2": {}
},
"anotherstring": {
"1": {},
"2": {}
}
},
"System": {
"Diagnostics": {
"1": {},
"2": {}
},
"Text": {
"1": {},
"2": {}
},
"Collections": {
"Generic": {
"1": {},
"2": {}
}
},
"Linq": {
"1": {},
"2": {}
}
}
}
I think my downfall was having to use NameSpace : Dictionary<string, NameSpace> due to the fact i didn't know how to better serialise in Json.Net. Also without spending time on this, i couldnt figure out how to convert the last node to an json array. though i'm not sure if you will be able to if you think about it. However i'm sure this can be done better
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 am looking to use JSON.net to deserialize a JSON structure into a Dictionary. The trick is that the JSON document is a hierarchy of nested objects but I'd like to only look at the top-level property+value pairs.
Eg.
{
"prop1": 142,
"prop2": "Some description",
"object_prop": {
"abc": 2,
"def": {
"foo": "hello",
"bar": 4
}
}
}
Based on the above example, I'd like to have my deserialized dictionary have 3 items in it: "prop1", "prop2", and "object_prop". "object_prop" should just be a string (which I will deserialize to an object at some later point in time.
Note: I'm looking to do this because I want to create a re-usable library that just knows about top-level key/value pairs and where clients consuming the library can define the type of the values at a later point in time. (ie. I don't want my re-usable library bound to the object types ... namely "object_prop").
How about something like this?
class Program
{
static void Main(string[] args)
{
string json = #"
{
""prop1"": 142,
""prop2"": ""Some description"",
""object_prop"": {
""abc"": 2,
""def"": {
""foo"": ""hello"",
""bar"": 4
}
},
""prop3"": 3.14
}";
Dictionary<string, string> dict = new Dictionary<string, string>();
JObject jo = JObject.Parse(json);
foreach (JProperty prop in jo.Properties())
{
if (prop.Value.Type == JTokenType.Array ||
prop.Value.Type == JTokenType.Object)
{
// JSON string for complex object
dict.Add(prop.Name, prop.Value.ToString(Formatting.None));
}
else
{
// primitive value converted to string
object value = ((JValue)prop.Value).Value;
dict.Add(prop.Name, value != null ? value.ToString() : null);
}
}
foreach (KeyValuePair<string, string> kvp in dict)
{
Console.WriteLine(kvp.Key + " = " + kvp.Value);
}
}
}
Output:
prop1 = 142
prop2 = Some description
object_prop = {"abc":2,"def":{"foo":"hello","bar":4}}
prop3 = 3.14
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
}