C
C#2y ago
malkav

❔ Convert nested JObjects into Dictionary<string, object>

What I want is like the title, to convert any json given by the user into a Dictionary<string, object> but keep the JSON structure. Especially if it's nested. So I figured I'd start recursively, in a sense, but I am getting stuck on the point where I attack the child tokens, since they are JToken and not JObject. And I'm not sure if they will keep working if I use OfType<JObject>(). So I could use some help converting a JObject that a user inputs to a Dictionary that keeps the JSON structure. Here's what I've got so far:
public static Dictionary<string,object> Convert(this JObject source)
{
Dictionary<string,object> result = new();
foreach (KeyValuePair<string, JToken> kvp in source)
{
if (kvp.Value.HasValues) // aka if it has child tokens according to NewtonSoft.Json docs??
{
foreach (var childToken in kvp.Value.Children().OfType<JObject>())
{
childToken.Convert();
}
}
}
}
public static Dictionary<string,object> Convert(this JObject source)
{
Dictionary<string,object> result = new();
foreach (KeyValuePair<string, JToken> kvp in source)
{
if (kvp.Value.HasValues) // aka if it has child tokens according to NewtonSoft.Json docs??
{
foreach (var childToken in kvp.Value.Children().OfType<JObject>())
{
childToken.Convert();
}
}
}
}
but as you can see I'm not doing anything with childTokens at the moment because I'm not sure how to keep the original JSON structure The reason I am trying to convert it, is because I have to do two of these mappings, and then map user-given keys of the one JSON object to the other (the values) So this part:
foreach (var prop in PropertiesToMap)
{
if (!dataTo.ContainsKey(prop.To) || !dataFrom.ContainsKey(prop.From)) continue;
dataTo[prop.To] = dataFrom[prop.From];
}
foreach (var prop in PropertiesToMap)
{
if (!dataTo.ContainsKey(prop.To) || !dataFrom.ContainsKey(prop.From)) continue;
dataTo[prop.To] = dataFrom[prop.From];
}
Please halp with my recursive function to map any json into a dictionary And afterwards, but this is optional for me, a query question
23 Replies
Relevant
Relevant2y ago
Seems like this would be simpler and nicer to just parse into strongly typed class objects. Is there a reason you don't want to go that route?
malkav
malkavOP2y ago
because the user is the one that provides a JSON but I never know what the JSON might look like. The idea is to map two JSON together. JObj_A has properties, which need to be mapped into JObj_B and then the API returns JObj_B with the new property values. The user gives me 4 inputs
JObj_A JObj_B QueryString (optional, and not for now) PropsToMap
The PropsToMap is of type PropsToMap[]
public class PropsToMap
{
[JsonProperty("from")] public string From {get;set;}
[JsonProperty("to")] public string To {get;set;}
}
public class PropsToMap
{
[JsonProperty("from")] public string From {get;set;}
[JsonProperty("to")] public string To {get;set;}
}
I don't know ahead of time what JObj_A or B look like, so I cannot make pre-declared classes and types for them hence the conversion to DIctionaries
Relevant
Relevant2y ago
ah okay, makes sense Do you have some idea in mind how this dictionary would look?
malkav
malkavOP2y ago
😅 I know, it's a bit messy, but it can be made clean if I do it right, but I am kind of stuck of recursively parsing JObjects into Dictionaries (note, JObj_A and JObj_B are both of type JObject) I know NewtonSoft.Json has this method JObj_A.ToObject<Dictionary<string, object>>() but that does not add the nested objects, it just stays first layer deep So this dictionary could look anything from: (coming up_
{ "string", "string" }
{ "string", "string" }
all the way up to:
{ "string", { "string", { "string", ... } } } (and possibly even deeper, but I expect a user to stick to 3 or 4 layers deep, with a few arrays in them and such
{ "string", { "string", { "string", ... } } } (and possibly even deeper, but I expect a user to stick to 3 or 4 layers deep, with a few arrays in them and such
The idea is that the JSON objects come from a seperate NoSQL database, or perhaps even an SQL database, adding all colums into JSON properties. I don't care where the user gets their data for A and B from. All I am supposed to do is map the properties to the right properties and return the B object 😅
Anton
Anton2y ago
just use the JObject it has more info than a dictionary it can be mapped easily it avoids extra allocations ob the dictionaries
malkav
malkavOP2y ago
How do I get the keys (and thus the values of these keys) in a JObject? can I just like Dicts use JObjectItem[keyString]?
Anton
Anton2y ago
Properties or something like that
Relevant
Relevant2y ago
Well what I mean is. let's say you have a parent in the dictionary. Then a child in the dictionary. How would each of those look? Something like:
{"TheParent", { "TheParent: { 'Child1': { 'Child2':... }}"}},
{"Child1", {"Child1: { 'Child2': .. }" } }
{"TheParent", { "TheParent: { 'Child1': { 'Child2':... }}"}},
{"Child1", {"Child1: { 'Child2': .. }" } }
Anton
Anton2y ago
you can index jobject with the key
malkav
malkavOP2y ago
so like this:
string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
RequestBody data = JsonConvert.DeserializeObject<RequestBody>(requestBody);

foreach (PropsMap prop in data.PropertiesToMap)
{
if (!data.DataMappingTo.ContainsKey(prop.To) || !data.DataToMap.ContainsKey(prop.From)) continue;
data.DataMappingTo[prop.To] = data.DataToMap[prop.From];
}

return new OkObjectResult(new ResponseBody
{
Result = JObject.FromObject(data.DataMappingTo)
});
string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
RequestBody data = JsonConvert.DeserializeObject<RequestBody>(requestBody);

foreach (PropsMap prop in data.PropertiesToMap)
{
if (!data.DataMappingTo.ContainsKey(prop.To) || !data.DataToMap.ContainsKey(prop.From)) continue;
data.DataMappingTo[prop.To] = data.DataToMap[prop.From];
}

return new OkObjectResult(new ResponseBody
{
Result = JObject.FromObject(data.DataMappingTo)
});
? where data is the following class:
public class RequestBody
{
[OpenApiProperty][JsonProperty("data_to_map")] public JObject DataToMap { get; set; }
[OpenApiProperty][JsonProperty("query")] public string? QueryString { get; set; }
[OpenApiProperty][JsonProperty("data_mapping_to")] public JObject DataMappingTo { get; set; }
[OpenApiProperty][JsonProperty("properties_to_map")] public PropsMap[] PropertiesToMap { get; set; }
}

public class PropsMap
{
[JsonProperty("from")] public string From { get; set; }
[JsonProperty("to")] public string To { get; set; }
}
public class RequestBody
{
[OpenApiProperty][JsonProperty("data_to_map")] public JObject DataToMap { get; set; }
[OpenApiProperty][JsonProperty("query")] public string? QueryString { get; set; }
[OpenApiProperty][JsonProperty("data_mapping_to")] public JObject DataMappingTo { get; set; }
[OpenApiProperty][JsonProperty("properties_to_map")] public PropsMap[] PropertiesToMap { get; set; }
}

public class PropsMap
{
[JsonProperty("from")] public string From { get; set; }
[JsonProperty("to")] public string To { get; set; }
}
can I use a query-string on the JObject too? because I can't really convert a SQL query string into Linq, or url querystring to linq 😅
Anton
Anton2y ago
idk what you meab by query string, but the above will work fine with jobjects if you want a filter system, you need to implement that yourself
malkav
malkavOP2y ago
Ah, right... it works, as long as my prop.From or prop.To is single layer deep. As soon as I have to go deeper into an object, I have to index it by [indexKey][indexKey2]...
Anton
Anton2y ago
or use something like OData well yeah, fix up the logic introduce another while loop but you can index with a nested key like "a.b.c" I think
malkav
malkavOP2y ago
introduce another while loop??? huh?
Anton
Anton2y ago
that might be easier well if that property list contains property paths, aka a->b->c, then you need to follow the path right
malkav
malkavOP2y ago
Either I ask the user to give a property key as if it's a path: key1/key2/... or I make a while loop to look for said key in the object, but then I have the chance that there are multiple instances of said property... but even with path I have to create some new logic to get to said property
Anton
Anton2y ago
your From and To have to be either delimited by some character strings of keys, or lists of keys either way, if you need your logic to be multilevel, you need to represent a complex path somehow
malkav
malkavOP2y ago
I think they will have to be delimited as a path, so with /. Because I expect the end-user to understand to path the property keys, and not really how to create a list of the properties leading up to the key they want
Anton
Anton2y ago
multiple instances? there's no such thing in json or in dictionaries for that matter
D.Mentia
D.Mentia2y ago
any json content can just be deserialized directly to Dictionary<string,object> without any extra work. But if you want to be able to use it, you'll want Dictionary<string,JObject>, again just deserialize to it directly, no work required
Anton
Anton2y ago
you have to write custom logic to follow the path either way
malkav
malkavOP2y ago
Yea, alright, I'll see if I can figure out some logic to follow the path 😅 this already feels a little tricky lol Thanks, we've come to another solution, and working from there now 😅 I didn't know I could just use the JObject the same way. Indexing with []
foreach (PropsMap prop in data.PropertiesToMap)
{
string fromPath = string.Join(".", prop.From.Split("/"));
string toPath = string.Join(".", prop.To.Split("/"));

data.DataMappingTo
.SelectToken(toPath)
.Replace(data.DataToMap
.SelectToken(fromPath));
}
foreach (PropsMap prop in data.PropertiesToMap)
{
string fromPath = string.Join(".", prop.From.Split("/"));
string toPath = string.Join(".", prop.To.Split("/"));

data.DataMappingTo
.SelectToken(toPath)
.Replace(data.DataToMap
.SelectToken(fromPath));
}
this seems to work! 😁
Accord
Accord2y ago
Was this issue resolved? If so, run /close - otherwise I will mark this as stale and this post will be archived until there is new activity.
Want results from more Discord servers?
Add your server