Reputation: 87
How to merge the object of the same key ignoring array wrapper([])?
Below example rows have properties named "elements[0]"
, "elements[1]"
and "elements[2]"
.
{
"addresses":[
"some address"
],
"rows":[
{
"elements[0]":{
"distance":{
"text":"227 mi",
"value":365468
},
"duration":{
"text":"3 hours 54 mins",
"value":14064
},
"status":"OK"
},
"elements[1]":{
"distance":{
"text":"94.6 mi",
"value":152193
},
"duration":{
"text":"1 hour 44 mins",
"value":6227
},
"status":"OK"
},
"elements[2]":{
"distance":{
"text":"2,878 mi",
"value":4632197
},
"duration":{
"text":"1 day 18 hours",
"value":151772
},
"status":"OK"
}
}
],
"status":[
"OK"
]
}
Expected is the element [{element0, element1, element2}].
{
"addresses":[
"some address"
],
"rows":[
{
"elements": [{
"distance": {
"text": "227 mi",
"value": 365468
},
"duration": {
"text": "3 hours 54 mins",
"value": 14064
},
"status": "OK"
},
{
"distance": {
"text": "94.6 mi",
"value": 152193
},
"duration": {
"text": "1 hour 44 mins",
"value": 6227
},
"status": "OK"
},
{
"distance": {
"text": "2,878 mi",
"value": 4632197
},
"duration": {
"text": "1 day 18 hours",
"value": 151772
},
"status": "OK"
}]}
],
"status":[
"OK"
]
}
The requirement is on the unknown JSON string so, can't create class/model. The above is just an example any generic code would be more helpful.
Updated :: Thanks @dbc. It looks promising, the order elements will ascending for sure however other keys are swapped
{
"elements[0]": "Value 0",
"elements[1]": "Value 1",
"anotherelement[0]": "Another value 0",
"anotherelement[1]": "Another value 1",
"status" : "OK",
"lastitem" : "yes"
}
result is as below. Is there a way the order of the items as is. I know in JSON it won't affect but just to want to see if possible
{
"status" : "OK",
"lastitem" : "yes",
"elements": [
"Value 0",
"Value 1"
],
"anotherelement": [
"Another value 0",
"Another value 1"
]
}
expected is
{
"elements": [
"Value 0",
"Value 1"
],
"anotherelement": [
"Another value 0",
"Another value 1"
],
"status" : "OK",
"lastitem" : "yes"
}
Upvotes: 1
Views: 855
Reputation: 116981
To restate your problem, you have an arbitrary JSON hierarchy that contains properties whose names end in numerical indices in brackets, like the following (where the values could be of any type):
{
"elements[0]": "Value 0",
"elements[1]": "Value 1",
"anotherelement[0]": "Another value 0",
"anotherelement[1]": "Another value 1"
}
And you would like to transform them into array-valued properties by stripping off the bracketed indices and grouping and combining all the values with identical stripped property names, like so:
{
"elements": [
"Value 0",
"Value 1"
],
"anotherelement": [
"Another value 0",
"Another value 1"
]
}
This can be done using LINQ to JSON to edit your JSON hierarchy. You will also need to use a regular expression to pick out matching property names and a LINQ group
statement to group together items with similar names.
The following extension method does the job:
public static partial class JsonExtensions
{
public static void FixElementArrays(this JToken root)
{
var regex = new Regex("^(.+)\\[[0-9]+\\]$");
if (root is JContainer container)
{
var query =
from o in container.DescendantsAndSelf().OfType<JObject>()
let matches = o.Properties()
.Select(p => (Property : p, Match : regex.Match(p.Name)))
.Where(m => m.Match.Success)
.Select(m => (m.Property, Name : m.Match.Groups[1].Value))
let groups = matches.GroupBy(m => m.Name)
from g in groups
select (Object : o, Name : g.Key, Values : g.Select(m => m.Property.Value).ToList());
foreach (var g in query.ToList())
{
IList<JToken> objAsList = g.Object;
// DescendantsAndSelf() returns items in document order, which ordering is preserved by GroupBy, so index of first item should be first index.
var insertIndex = objAsList.IndexOf(g.Values[0].Parent);
g.Values.ForEach(v => v.RemoveFromLowestPossibleParent());
objAsList.Insert(insertIndex, new JProperty(g.Name, new JArray(g.Values)));
}
}
}
public static JToken RemoveFromLowestPossibleParent(this JToken node)
{
if (node == null)
return null;
// If the parent is a JProperty, remove that instead of the token itself.
var property = node.Parent as JProperty;
var contained = property ?? node;
if (contained.Parent != null)
contained.Remove();
// Also detach the node from its immediate containing property -- Remove() does not do this even though it seems like it should
if (property != null)
property.Value = null;
return node;
}
}
Which you can use as follows:
var rootToken = JToken.Parse(jsonString);
rootToken.FixElementArrays();
var fixedJsonString = rootToken.ToString();
Notes:
You may need to tweak the regular expression based on your actual JSON property names. For instance, it's not clear from your question what to do with a name like "[0][1]"
.
The code assumes that the "elements[*]"
properties are already in correct order, i.e. not
{
"elements[3]": "Value 3",
"elements[1]": "Value 1",
"elements[2]": "Value 2"
}
And as such puts them into the final array in the order they are encountered, rather than trying to order then by the index number inside the "elements[*]"
property name.
Demo fiddle here.
Upvotes: 1