Custom JSON.net deserialization into a Dictionary<string, string> - c#

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

Related

use Newtonsoft to parse Configuration files in .net 5

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

Newton JSON serialize complex key collection into JSON

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.

convert IDictionary data into JSON string using NewtonJSON

how can I convert IDictionary data into JSON using NewtonJSON. My IDictionary data contains the following data:
type: 19
id : 4433
Now I want to convert it into
{
"type":"19",
"id": "4433"
}
How do I do this?
IDictionary<string, string> messageData = message.Data;
var json = JsonConvert.SerializeObject(messageData, new JsonSerializerSettings()
{
Formatting = Formatting.Indented
});
here is the update please see my screenshots
Json.NET (formely Newtonsoft.Json) already has the built in capability to convert dictionaries into Json objects:
// the dictionary may be anything IDictionary<string, whatever>, Json.NET will convert it anyway
IDictionary<string, string> dict = new Dictionary<string, string>()
{
{ "type", "19" },
{ "id" ,"4433"}
};
var json = JsonConvert.SerializeObject(dict, new JsonSerializerSettings()
{
Formatting = Formatting.Indented
});
Outputs:
{
"type": "19",
"id": "4433"
}
Demo: https://dotnetfiddle.net/a562kK
[Edit]
The type you are trying to serialize is not an IDictionary at all. You should try to convert it to a dictionary first.
Here an example (assuming message.Data implements at least IEnumerable):
var dict = new Dictionary<string, string>();
foreach(var item in message.Data)
{
// get Key and Value from item here
var kvp = item as KeyValuePair<string, string>; // this is just an example, I do not know what type your message.Data is returning
dict.Add(kvp.Key, kvp.Value);
}
// now you may serialize `dict`
var list = d.Select(x => new obj{ type = x.Key, id = x.Value});
var json = JsonConvert.SerializeObject(list);
Class:
public class obj
{
public string type { get; set; }
public string id { get; set;}
}

How does Dictionary<string, object> know the type of the value?

Consider this code:
JObject obj = JObject.Parse(json);
Dictionary<string,object> dict = new Dictionary<string, object>();
List<string> parameters = new List<string>{"Car", "Truck", "Van"};
foreach (var p in parameters)
{
JToken token = obj.SelectToken(string.Format("$.results[?(#.paramName == '{0}')]['value']", p));
dict[p] = token.Value<object>();
}
string jsonOutput = JsonConvert.SerializeObject(dict);
where json contains, in part:
{
"paramName": "Car",
"value": 68.107
},
{
"paramName": "Truck",
"value": 48.451
},
{
"paramName": "Van",
"value": 798300
}
While debugging this, I inspected the dictionary and found the values were not of type object but actual numeric types like integer and float. Since the dictionary was declared as Dictionary<string, object> dict = new Dictionary<string, object>(); I expected the values to be of type object and that I would need to cast them upon use.
The JSON output string is {"Car":68.107,"Truck":48.451,"Van":798300}.
How does the dictionary know the type of the value and why do I get the actual type instead of the base type object?
When you call
JsonConvert.SerializeObject(dict);
this takes an object, which then figures out the type, and stores it accordingly.
Therefore for each item in the dictionary entry. it first checks the type and then deserializes it according to its type.
If you wanted to use the object outside of JSON, you would have to check yourself with something like
var o = dict["Car"];
if(o is int) return (int)o; // or whatever you want to do with an int
if(o is decimal) return (decimal)o; // or whatever you want to do with an decimal
When you inspect an instance in the debugger, the debugger just shows you the output of the .ToString() for the item. Run the following code:
namespace ConsoleApplication1
{
public class Program
{
private static void Main(string[] args)
{
var dic = new Dictionary<string, object>
{
{ "One", "1" }, { "Two", 2 },
{ "Three", 3.0 }, { "Four", new Person() },
{ "Five", new SimplePerson() },
};
foreach (var thisItem in dic)
{
// thisItem.Value will show "1", 2, 3, "Test"
// and ConsoleApplication1.SimplePerson
Console.WriteLine($"{ thisItem.Key } : { thisItem.Value }");
}
var obj = JsonConvert.SerializeObject(dic);
}
}
public class SimplePerson
{
}
public class Person
{
public override string ToString()
{
return "Test";
}
}
}
When you hover over the this.Value, it will show the result of ToString(). So it will show the following:
"1"
2
3
"Test" // since Person class overrides the ToString()
// since by default when ToString() is called it will print out the object.
ConsoleApplication1.SimplePerson
Serialization
For serialization, it will produce the following:
{
"One": "1",
"Two": 2,
"Three": 3.0,
"Four": {
},
"Five": {
}
}
They are all boxed as object types but when the underlying type is whatever the type is. So in our case String",Int,Double,PersonandSimplePerson`. Therefore, during serialization it is unboxed.

How to store values from JSON to key-value pairs

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

Categories

Resources