Merge two Json.NET arrays by concatenating contained elements - c#

I have two JToken's that represent JSON arrays of objects and I would like to merge them. JToken has a method Concat but it produces null as result when I try to use it.
Action<JToken> Ok = (x) =>
{
Debug.WriteLine(x);
/* outputs
[
{
"id": 1,
},
{
"id": 2,
}
]
*/
x = (x).Concat<JToken>(x) as JToken;
Debug.WriteLine(x); // null
};
How can I make it work?

Use JContainer.Merge() with MergeArrayHandling.Concat.
This is available starting with Json.NET 6 Release 4. So if your arrays are in a JContainer (e.g. a JObject), this is a simple and robust solution.
Example:
JObject o1 = JObject.Parse(#"{
'FirstName': 'John',
'LastName': 'Smith',
'Enabled': false,
'Roles': [ 'User' ]
}");
JObject o2 = JObject.Parse(#"{
'Enabled': true,
'Roles': [ 'Operator', 'Admin' ]
}");
o1.Merge(o2, new JsonMergeSettings { MergeArrayHandling = MergeArrayHandling.Concat });
string json = o1.ToString();
// {
// "FirstName": "John",
// "LastName": "Smith",
// "Enabled": true,
// "Roles": [
// "User",
// "Operator",
// "Admin"
// ]
// }

JToken.FromObject(x.Concat(x))

I needed same, here's what I came up with
https://github.com/MrAntix/Newtonsoft.Json/blob/master/Src/Newtonsoft.Json/Linq/MergeExtensions.cs
public static void MergeInto(
this JContainer left, JToken right, MergeOptions options)
{
foreach (var rightChild in right.Children<JProperty>())
{
var rightChildProperty = rightChild;
var leftPropertyValue = left.SelectToken(rightChildProperty.Name);
if (leftPropertyValue == null)
{
// no matching property, just add
left.Add(rightChild);
}
else
{
var leftProperty = (JProperty) leftPropertyValue.Parent;
var leftArray = leftPropertyValue as JArray;
var rightArray = rightChildProperty.Value as JArray;
if (leftArray != null && rightArray != null)
{
switch (options.ArrayHandling)
{
case MergeOptionArrayHandling.Concat:
foreach (var rightValue in rightArray)
{
leftArray.Add(rightValue);
}
break;
case MergeOptionArrayHandling.Overwrite:
leftProperty.Value = rightChildProperty.Value;
break;
}
}
else
{
var leftObject = leftPropertyValue as JObject;
if (leftObject == null)
{
// replace value
leftProperty.Value = rightChildProperty.Value;
}
else
// recurse object
MergeInto(leftObject, rightChildProperty.Value, options);
}
}
}
}

Related

How to transform or convert dot (.) appended flat json structure to tree view (regular) json

I have below Json, I need to transform this dotted flat hierarchy into tree view hierarchy
{
"info.clinical.user.gender.1": "female",
"info.clinical.user.gender.2": "male",
"info.clinical.user.age.max": "60",
"info.clinical.user.age.min": "18",
}
How to convert above Json to below JSON in a best and optimized way using C#
{
"info": {
"clinical": {
"user": {
"age": {
"min": 18,
"max": 60
},
"gender": [
"female",
"male"
]
}
}
}
}
try this
var origJsonObj = JObject.Parse(json);
var arrFirst = origJsonObj.Properties().First().Name.Split('.');
var jsonObj = new JObject();
var prevObj = jsonObj;
for (int i = 0; i < arrFirst.Length - 2; i++)
{
var currObj = new JObject();
var newProperty = new JProperty(arrFirst[i], currObj);
prevObj.Add(newProperty);
prevObj = currObj;
}
foreach (var prop in origJsonObj.Properties())
{
var arr = prop.Name.Split('.');
var name = arr[arr.Length - 2];
if (int.TryParse(arr[arr.Length - 1], out var index))
{
JArray jArr;
if (prevObj[name] == null)
{
jArr = new JArray();
var newProperty = new JProperty(arr[arr.Length - 2], jArr);
prevObj.Add(newProperty);
}
else jArr = (JArray)prevObj[name];
jArr.Add(prop.Value);
continue;
}
JObject jObj = null;
if (prevObj[name] == null)
{
jObj = new JObject();
prevObj.Add(new JProperty(name, jObj));
}
else jObj = (JObject)prevObj[name];
jObj[arr[arr.Length - 1]] = prop.Value;
}
Maybe you should implement it yourself, like:
using System.Text.Json.Nodes;
namespace FlatJson
{
public static class FlatJson
{
public static JsonObject ToTree(this JsonObject flat, char sep = '.')
{
var result = new JsonObject();
foreach (var flatItem in flat)
{
var layerNames = flatItem.Key.Split(sep);
var lastIndex = layerNames.Length - 1;
var currentLayer = result;
for (int layerIndex = 0; layerIndex < lastIndex; layerIndex++)
{
var layerName = layerNames[layerIndex];
var nextLayer = currentLayer[layerName]?.AsObject();
if (nextLayer is not null)
currentLayer = nextLayer;
else
{
nextLayer = new JsonObject();
currentLayer[layerName] = nextLayer;
currentLayer = nextLayer;
}
}
var propertyName = layerNames[lastIndex];
var value = flatItem.Value?.GetValue<object?>();
currentLayer[propertyName] = JsonValue.Create(value);
}
return result;
}
}
}
And how to use it:
using System.Diagnostics;
using System.Text.Json.Nodes;
using FlatJson;
var t = File.ReadAllText("test.json");
JsonObject? obj = JsonNode.Parse(t)?.AsObject();
Debug.Assert(obj is not null);
JsonObject result = obj.ToTree();
Console.WriteLine(obj);
Console.WriteLine();
Console.WriteLine(result);
But as I asked in the comments, we don't really know whether the "60" here is a number or not, and whether the info.clinical.user.gender is a list or an object. So the output of my code is (which makes sense to me):
(The original json has been changed to show you how it works in some other situations.)
{
"info.clinical.user.gender.1": "60",
"info.clinical.user.gender.2": "male",
"info.clinical.user.age.max": 60,
"info.clinical.user.age.min": null
}
{
"info": {
"clinical": {
"user": {
"gender": {
"1": "60",
"2": "male"
},
"age": {
"max": 60,
"min": null
}
}
}
}
}

Filter JSON Array with dynamic conditions

I have many JSON array with different types of nodes in it.
Sample Json 1:
[
{
"EmpID": "23",
"EmpName": "Jhon",
"Age": "23"
},
{
"EmpID": "29",
"EmpName": "Paul",
"Age": "25"
},
{
"EmpID": "123",
"EmpName": "Jack",
"Age": "29"
},
{
"EmpID": "129",
"EmpName": "Apr",
"Age": "29"
}
]
Sample Json 2
[
{
"DepID": "2",
"Name": "Sales"
},
{
"DepID": "5",
"Name": "Marketing"
},
{
"DepID": "12",
"Name": "IT"
}
]
I want to filter them based on different conditions such as
1)EmpID=29
This should return
[
{
"EmpID": "29",
"EmpName": "Paul",
"Age": "25",
}
]
2)Age=23 and EmpName=Jhon
This should return
[
{
"EmpID": "23",
"EmpName": "Jhon",
"Age": "23"
}
]
Age=29
This should return
[
{
"EmpID": "123",
"EmpName": "Jack",
"Age": "29"
},
{
"EmpID": "129",
"EmpName": "Apr",
"Age": "29"
}
]
So I need a generic approach to do any number of filters on the JSON array. I am planning to get all the filters using some comma separated string like Age="23",EmpName="Jhon" and this can be converted to any format in the code.
I have tried creating dynamic filter using Json Path such as $.[?(#.Age == '23' && #.EmpName == 'Jhon')].
Also I tried using LINQ like
var result = JsonConvert.DeserializeObject(jsonString);
var res = (result as Newtonsoft.Json.Linq.JArray).Where(x =>
x["Age"].ToString() =="23" && x["EmpName"].ToString()=="Jhon").ToList();
But how I can generate the where conditions dynamically based on any number of conditions I receive
Also there is a plan to include Date filters in case there is some datetime nodes in json such as BirthDate>12051995.
I am not sure how I can dynamically filter using any number of input filter conditions.
To get this working in a traditional way, you'll need to perform 3 steps:
define a class to contain the data
deserialize the json into a list of objects
use linq to query your selection
You can do the same thing for the departments.
If you need to join them in any way, use .Join. If the JSON is mixed, you can create a single class containing all the properties and use that to query.
So for the simple case: first define a class to represent you object:
public class Employee
{
public int EmpID {get;set;}
public string EmpName {get;set;}
public int Age {get;set;}
}
Then deserialize and query:
put at the top:
using System.Text.Json;
public void Main()
{
//deserialize into a list
List<Employee> employees =
JsonSerializer.Deserialize<List<Employee>>(yourJsonString);
//query
var result = employees.Where(c => c.Age == 23 && c.EmpName == "Jhon");
//show results
foreach (var employee in result)
Console.WriteLine(employee.EmpID);
}
As by update:
Depending on your use case you have a couple of options:
a fixed number of dynamic properties
a truly dynamic query
A fixed number of dynamic properties
You can achieve a more dynamic setup with the following:
//define the filterable properties
//note they are nullable
int? age = null;
int? id = null;
string name = null;
//apply them in a query
//
//note: if one of the filter properties is not set,
// that side of the && expression evaluates to "true"
var result = employees.Where(c => (age == null ? true : c.Age == age) &&
(id == null ? true : c.EmpId == id) &&
(name == null ? true : c.EmpName == name));
a truly dynamic query
Now here things start to get tricky. One possible option is to generate a string based query, with the help of a libary like Dynamic Linq
You have almost nailed it. :)
Instead of using DeserializeObject and then converting it to JArray prefer JArray.Parse
var json = File.ReadAllText("sample.json");
var semiParsedJson = JArray.Parse(json);
Instead of using ToList after Where prefer JArray constructor which can work well with an IEnumerable<JToken>
const string IdField = "EmpID", NameField = "EmpName", AgeField = "Age";
const StringComparison caseIgnorant = StringComparison.OrdinalIgnoreCase;
var idEq29 = semiParsedJson.Children()
.Where(token => string.Equals(token[IdField].Value<string>(),"29", caseIgnorant));
Console.WriteLine(new JArray(idEq29).ToString());
The other queries can be implemented in the very same way
var ageEq23AndNameJhon = semiParsedJson.Children()
.Where(token => string.Equals(token[AgeField].Value<string>(), "23", caseIgnorant)
&& string.Equals(token[NameField].Value<string>(), "Jhon", caseIgnorant));
Console.WriteLine(new JArray(ageEq23AndNameJhon).ToString());
var ageEq29 = semiParsedJson.Children()
.Where(token => string.Equals(token[AgeField].Value<string>(), "29", caseIgnorant));
Console.WriteLine(new JArray(ageEq29).ToString());
UPDATE #1: Enhance proposed solution
With the following extension method
public static class JArrayExtensions
{
public static JArray Filter(this JArray array, Func<JToken, bool> predicate)
=> new JArray(array.Children().Where(predicate));
}
you can greatly simplify the filtering
var idEq29 = semiParsedJson
.Filter(token => string.Equals(token[IdField].Value<string>(),"29", caseIgnorant));
var ageEq23AndNameJhon = semiParsedJson
.Filter(token => string.Equals(token[AgeField].Value<string>(), "23", caseIgnorant))
.Filter(token => string.Equals(token[NameField].Value<string>(), "Jhon", caseIgnorant));
var ageEq29 = semiParsedJson
.Filter(token => string.Equals(token[AgeField].Value<string>(), "29", caseIgnorant));
Console.WriteLine(idEq29);
Console.WriteLine();
Console.WriteLine(ageEq23AndNameJhon);
Console.WriteLine();
Console.WriteLine(ageEq29);
Or you can push it even further. If all the fields store string values then you can define the extension method like this:
public static class JArrayExtensions
{
public static JArray Filter(this JArray array, string field, string value)
=> new JArray(array.Children().Where(GenerateFilter(field, value)));
private static Func<JToken, bool> GenerateFilter(string field, string value)
=> (JToken token) => string.Equals(token[field].Value<string>(), value, StringComparison.OrdinalIgnoreCase);
}
The the filter queries are super simple :D
var idEq29 = semiParsedJson
.Filter(IdField,"29");
var ageEq23AndNameJhon = semiParsedJson
.Filter(AgeField, "23")
.Filter(NameField, "Jhon");
var ageEq29 = semiParsedJson
.Filter(AgeField, "29");
Console.WriteLine(ageEq23AndNameJhon);
Console.WriteLine();
Console.WriteLine(idEq29);
Console.WriteLine();
Console.WriteLine(ageEq29);

removing properties from complex json object

I am trying to remove some properties which are dynamic input from JObject(converted json to JObject). i can do this on parent elements but not the nested child elements.
INPUT
{
"id": 1,
"name": "something",
"marks": [
{
"pid": 1000,
"sub": "AAA",
"rank": 2
},
{
"pid": 1001,
"sub": "BBB",
"rank": 10
}
]
}
Now i wand to remove the id from parent and pid from each marks property in json. This list is dynamic and may grow in future. data is above provided is an example but the original list(marks in example) contains more than 20 properties.
CODE TRIED (which is deleting only id property from the parent)
string[] props = new string[] { "id", "pid" };
JObject jObject = JsonConvert.DeserializeObject<JObject>(str.ToLower());
if (jObject != null)
{
jObject.Properties()
.Where(attr => props.Contains(attr.Name))
.ToList()
.ForEach(attr => attr.Remove());
}
You can create a function and call it recursively if the nested values are JObject or JArray, like the following code:
1 - Create the main function RemoveIds:
public static void RemoveIds(JObject jObject, string[] props)
{
List<JProperty> jProperties = jObject.Properties().ToList();
for (int i = 0; i < jProperties.Count; i++)
{
JProperty jProperty = jProperties[i];
if (jProperty.Value.Type == JTokenType.Array)
{
RemoveFromArray((JArray)jProperty.Value, props);
}
else if (jProperty.Value.Type == JTokenType.Object)
{
RemoveIds((JObject)jProperty.Value, props);
}
else if (props.Contains(jProperty.Name))
{
jProperty.Remove();
}
}
}
2 - Create simple method RemoveFromArray that call the main function inside a loop:
private static void RemoveFromArray(JArray jArray, string[] props)
{
foreach(JObject jObject in jArray)
{
RemoveIds(jObject, props);
}
}
3 - Call main function in the code like :
JObject jObject = JsonConvert.DeserializeObject<JObject>(json.ToLower());
if (jObject != null)
{
RemoveIds(jObject, new string[] { "id", "pid" });
Console.WriteLine(jObject);
}
4 - Result:
{
"name": "something",
"marks": [
{
"sub": "aaa",
"rank": 2
},
{
"sub": "bbb",
"rank": 10
}
]
}
I hope you find this helpful.
The answer from #Mohammed Sajid was exactly what I was looking for! However, I found that if the property I was wanting to remove was itself an object, it did not get removed. I switched the order of the if/else tests to put the test for removing first which solved my problem. On top of this, since I'm only ever searching for a single property to remove in a large and complex object, I also added an optional findOnce parameter which, if set to true will quit out of the iteration and recursion loops as soon as the property has been removed, thereby saving a few runs around the loop.
public static bool RemoveIds(JObject jObject, string props, in bool findOnce = true)
{
List<JProperty> jProperties = jObject.Properties().ToList();
bool Found = false;
for (int i = 0; i < jProperties.Count; i++)
{
if (findOnce && Found) break;
JProperty jProperty = jProperties[i];
if (props == jProperty.Name)
{
jProperty.Remove();
Found = true;
}
else if (jProperty.Value.Type == JTokenType.Array)
{
Found = RemoveFromArray((JArray)jProperty.Value, props, findOnce);
}
else if (jProperty.Value.Type == JTokenType.Object)
{
Found = RemoveIds((JObject)jProperty.Value, props, findOnce);
}
}
return Found;
}
private static bool RemoveFromArray(JArray jArray, string props, in bool findOnce = true )
{
bool Found = false;
foreach (JObject jObject in jArray)
{
Found = RemoveIds(jObject, props, findOnce);
if( findOnce && Found ) return true;
}
return Found;
}

Convert flat JSON to nested JSON

I have a flat JSON like below (I don't know what to call it, I hope that flat is the right word)
{
"id":12947,
"name.first_name":"Honey",
"address.street.number":"23",
"address.city.code":"LL",
"address.street.name":"Narrow Street",
"address.city.name":"Lalaland",
"name.last_name":"Moon",
"books": [
{
"title":"The long story",
"author.first_name":"Brew",
"author.last_name":"Beating",
"type":"novel"
},
{
"title":"Money and morality",
"author.first_name":"Chris",
"author.last_name":"Mas",
"type":"self-help"
}
]
}
Please notice that the fields are not in sorted order.
I want to convert it into a nested JSON like below:
{
"id":12947,
"name":{
"first_name":"Honey",
"last_name":"Moon"
},
"address":{
"street":{
"number":"23",
"name":"Narrow Street"
},
"city":{
"code":"LL",
"name":"Lalaland"
}
},
"books": [
{
"title":"The long story",
"author": {
"first_name":"Brew",
"last_name":"Beating"
},
"type":"novel"
},
{
"title":"Money and morality",
"author":{
"first_name":"Chris",
"last_name":"Mas"
},
"type":"self-help"
}
]
}
What is a good algorithm to convert it?
I am a C# person, I intend to use Newtonsoft.Json to parse the input JSON to a JObject, then iterate through all fields to check their keys and create nested JObjects. For arrays, I repeat the same process for every array item.
Do you have any better idea?
This is my solution for those who are interested.
public static string ConvertFlatJson(string input)
{
var token = JToken.Parse(input);
if (token is JObject obj)
{
return ConvertJObject(obj).ToString();
}
if (token is JArray array)
{
return ConvertArray(array).ToString();
}
return input;
}
private static JObject ConvertJObject(JObject input)
{
var enumerable = ((IEnumerable<KeyValuePair<string, JToken>>)input).OrderBy(kvp => kvp.Key);
var result = new JObject();
foreach (var outerField in enumerable)
{
var key = outerField.Key;
var value = outerField.Value;
if (value is JArray array)
{
value = ConvertArray(array);
}
var fieldNames = key.Split('.');
var currentObj = result;
for (var fieldNameIndex = 0; fieldNameIndex < fieldNames.Length; fieldNameIndex++)
{
var fieldName = fieldNames[fieldNameIndex];
if (fieldNameIndex == fieldNames.Length - 1)
{
currentObj[fieldName] = value;
continue;
}
if (currentObj.ContainsKey(fieldName))
{
currentObj = (JObject)currentObj[fieldName];
continue;
}
var newObj = new JObject();
currentObj[fieldName] = newObj;
currentObj = newObj;
}
}
return result;
}
private static JArray ConvertArray(JArray array)
{
var resultArray = new JArray();
foreach (var arrayItem in array)
{
if (!(arrayItem is JObject))
{
resultArray.Add(arrayItem);
continue;
}
var itemObj = (JObject)arrayItem;
resultArray.Add(ConvertJObject(itemObj));
}
return resultArray;
}

Find pattern in json with json.net and linq

I'm searching a json file, with the following structure:
{
"objects": [
{
"name": "obj1",
"state": {
"type": 4,
"childs": [
"state": {
"type": 5,
...
The state can contain state as a child until any number of Levels. Now im trying to find all objects containing a certain Patterns of states, e.g. state 4 with child state 5 with child state 2.
My code so far is this.
JObject o = JObject.Parse(System.IO.File.ReadAllText(#"j.json"));
var oObjects=
from p in o["objects"]
where (string)p["state"] == "4"
select (string)p["name"];
How can I expand the code to find all objects containing the search pattern on any Level?
To make it work for indefinite level, then you will need to use a recursive method like the following:
void Main()
{
var str = #"{
""objects"": [
{
""name"": ""obj1"",
""state"": {
""type"": 4,
""childs"": [
{
""state"": {
""type"": 5
}
}
]
}
}
]
}";
var obj = JObject.Parse(str);
GetValidObjects(obj, new string[] { "4", "5" }); // Name list of valid objects
}
And the helper methods defined like:
public IEnumerable<string> GetValidObjects(JObject obj, IEnumerable<string> values)
{
return obj["objects"]
.Where(i => (string)i["state"]["type"] == values.First() && ContainsState((JArray)i["state"]["childs"], values.Skip(1)))
.Select(i => (string)i["name"]);
}
public bool ContainsState(JArray childs, IEnumerable<string> values)
{
if (childs == null)
{
return values.Count() == 0;
}
return childs.Any(i => (string)i["state"]["type"] == values.First() && ContainsState((JArray)i["state"]["childs"], values.Skip(1)));
}
An option could be to convert the json to xml and then use an xpath query to obtain the list of nodes.
string json = System.IO.File.ReadAllText(#"j.json");
XmlDocument document = (XmlDocument)JsonConvert.DeserializeXmlNode(json);
XmlNodeList nodes = document.SelectNodes("//name[../state[type[.=4] and childs/state[type[.=5] and childs/state[type[.=2]]]]]");
You can use SelectTokens for this:
var objects = o.SelectTokens("$.objects[?(#.state.type == 4
&& #.state.childs[*].state.type == 5)].name")
.Select(s => (string)s)
.ToList();

Categories

Resources