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;
}
Related
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
}
}
}
}
}
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;
}
XPath 2 states that the nodes order of a selection should be returned in their order in the document.
It looks this is not the case when you SelectTokens(JSONPath) in JSON.Net
When I process the following document
string json = #"
{
""Files"": {
""dir1"": {
""Files"": {
""file1.1.txt"": {
""size:100""},
""file1.2.txt"": {
""size:100""}
}
},
""dir2"": {
""Files"": {
""file2.1.txt"": {
""size:100""},
""file2.2.txt"": {
""size:100""}
}
},
""file3.txt"": {
""size:100""}
}
}";
The order is the following when using JSON.net SelectTokens("$..files.*")
dir1
dir2
file3.txt
file1.1.txt
file1.2.txt
file2.1.txt
file2.2.txt
When I expected the following order (as Xpath //files/*)
dir1
file1.1.txt
file1.2.txt
dir2
file2.1.txt
file2.2.txt
file3.txt
How should I write my query so that I get a List in the XPath order ?
Short of modifying the Json.Net source code, there is not a way that I can see to directly control what order SelectTokens() returns its results. It appears to be using breadth-first ordering.
Instead of using SelectTokens(), you could use a LINQ-to-JSON query with the Descendants() method. This will return tokens in depth-first order. However, you would need to filter out the property names you are not interested in, like "Files" and "size".
string json = #"
{
""Files"": {
""dir1"": {
""Files"": {
""file1.1.txt"": { ""size"": 100 },
""file1.2.txt"": { ""size"": 100 }
}
},
""dir2"": {
""Files"": {
""file2.1.txt"": { ""size"": 100 },
""file2.2.txt"": { ""size"": 100 }
}
},
""file3.txt"": { ""size"": 100 }
}
}";
JObject jo = JObject.Parse(json);
var files = jo.Descendants()
.OfType<JProperty>()
.Select(p => p.Name)
.Where(n => n != "Files" && n != "size")
.ToArray();
Console.WriteLine(string.Join("\n", files));
Fiddle: https://dotnetfiddle.net/yRAev4
If you don't like that idea, another possible solution is to use a custom IComparer<T> to sort the selected properties back into their original document order after the fact:
class JPropertyDocumentOrderComparer : IComparer<JProperty>
{
public int Compare(JProperty x, JProperty y)
{
var xa = GetAncestors(x);
var ya = GetAncestors(y);
for (int i = 0; i < xa.Count && i < ya.Count; i++)
{
if (!ReferenceEquals(xa[i], ya[i]))
{
return IndexInParent(xa[i]) - IndexInParent(ya[i]);
}
}
return xa.Count - ya.Count;
}
private List<JProperty> GetAncestors(JProperty prop)
{
return prop.AncestorsAndSelf().OfType<JProperty>().Reverse().ToList();
}
private int IndexInParent(JProperty prop)
{
int i = 0;
var parent = (JObject)prop.Parent;
foreach (JProperty p in parent.Properties())
{
if (ReferenceEquals(p, prop)) return i;
i++;
}
return -1;
}
}
Use the comparer like this:
JObject jo = JObject.Parse(json);
var files = jo.SelectTokens("$..Files")
.OfType<JObject>()
.SelectMany(j => j.Properties())
.OrderBy(p => p, new JPropertyDocumentOrderComparer())
.Select(p => p.Name)
.ToArray();
Console.WriteLine(string.Join("\n", files));
Fiddle: https://dotnetfiddle.net/xhx7Kk
I have the following Json:
{
"last_ts": "20161001154251",
"first_ts": "20061130072151",
"years": {
"2006": [
0,0,0,2,0,0,0,0,0,0,1,0
],
"2007": [
0,0,3,0,0,1,0,0,0,0,1,0
],
"2008": [.........],
.
.
.
}
}
I wanted to read each year's name and its corresponding array of numbers, I tried the following code:
JObject jObj = JObject.Parse(json);
var yearsA = jObj["years"];
foreach (var year in yearsA)
{
string yearStr = year. // code here to retrieve this year's name
foreach (var month in year.Children<JArray>().Children()) // loop thru each array value
{
int m = (int)month;
if(m > 0)
{
years.Add(yearStr);
break;
}
}
}
What I want now is just a way to get the name of the array, I tried lot of solutions but none worked for me.
Try this code:
var yearsA = jObj["years"].Cast<JProperty>();
List<string> years = new List<string>();
foreach (var year in yearsA)
{
foreach (var month in year.Children<JArray>().Children()) // loop thru each array value
{
int m = (int) month;
if (m > 0)
{
years.Add(year.Name);
break;
}
}
}
Just declare a class like
public class MyObj
{
public string last_ts { set; get; }
public string first_ts { set; get; }
public Dictionary<int,int[]> years { set; get; }
}
and deserialize as
var data = JsonConvert.DeserializeObject<MyObj>(jsonString);
Sample usage:
foreach(var entry in data.years)
{
int year = entry.Key;
int[] months = entry.Value.Where(m => m > 0).ToArray();
Console.WriteLine(year + " => " + string.Join(",", months));
}
If you want to use Linq to JSON, you can do something like this, but there's lot of other options, as mentioned in another answers.
string json = #"{
'last_ts': '20161001154251',
'first_ts': '20061130072151',
'years': {
'2006': [
0,0,0,2,0,0,0,0,0,0,1,0
],
'2007': [
0,0,3,0,0,1,0,0,0,0,1,0
],
}
}";
JObject jObj = JObject.Parse(json);
// since in your case your years and months are structure as key/value, it's possible to use built in class like Dictionary<TKey, TValue>
var years = jObj["years"].ToObject<Dictionary<string, List<int>>>();
var result = new Dictionary<string, List<int>>();
foreach (var year in years)
{
string key = year.Key;
var value = year.Value;
var months = new List<int>();
value.ForEach(t =>
{
if (t > 0)
{
months.Add(t);
}
});
result.Add(key, months);
}
Look at Convert JObject into Dictionary. Is it possible?
and How do I get the list of keys in a dictionary?
I have a json like the following:
{
"d": {
"results": [
{
"__metadata": {
},
"prop1": "value1",
"prop2": "value2",
"__some": "value"
},
{
"__metadata": {
},
"prop3": "value1",
"prop4": "value2",
"__some": "value"
},
]
}
}
I just want to transform this JSON into a different JSON. I want to strip out the "_metadata" and "_some" nodes from the JSON. I'm using JSON.NET.
I just ended up deserializing to JObject and recursively looping through that to remove unwanted fields. Here's the function for those interested.
private void removeFields(JToken token, string[] fields)
{
JContainer container = token as JContainer;
if (container == null) return;
List<JToken> removeList = new List<JToken>();
foreach (JToken el in container.Children())
{
JProperty p = el as JProperty;
if (p != null && fields.Contains(p.Name))
{
removeList.Add(el);
}
removeFields(el, fields);
}
foreach (JToken el in removeList)
{
el.Remove();
}
}
Building off of #[Mohamed Nuur]'s answer, I changed it to an extension method which I think works better:
public static JToken RemoveFields(this JToken token, string[] fields)
{
JContainer container = token as JContainer;
if (container == null) return token;
List<JToken> removeList = new List<JToken>();
foreach (JToken el in container.Children())
{
JProperty p = el as JProperty;
if (p != null && fields.Contains(p.Name))
{
removeList.Add(el);
}
el.RemoveFields(fields);
}
foreach (JToken el in removeList)
{
el.Remove();
}
return token;
}
Here is unit test:
[TestMethod]
public void can_remove_json_field_removeFields()
{
string original = "{\"d\":{\"results\":[{\"__metadata\":{},\"remove\":\"done\",\"prop1\":\"value1\",\"prop2\":\"value2\",\"__some\":\"value\"},{\"__metadata\":{},\"prop3\":\"value1\",\"prop4\":\"value2\",\"__some\":\"value\"}],\"__metadata\":{\"prop3\":\"value1\",\"prop4\":\"value2\"}}}";
string expected = "{\"d\":{\"results\":[{\"prop1\":\"value1\",\"prop2\":\"value2\",\"__some\":\"value\"},{\"prop3\":\"value1\",\"prop4\":\"value2\",\"__some\":\"value\"}]}}";
string actual = JToken.Parse(original).RemoveFields(new string[]{"__metadata", "remove"}).ToString(Newtonsoft.Json.Formatting.None);
Assert.AreEqual(expected, actual);
}
I would create a new data structure with only the required information and copy the data from the first one. Often that is the simpliest approach. Just an idea.
This answer applies if you have a JArray with JTokens, not JObjects:
Here is an example:
string json = "[null, null, \"x\", null, null, null, 0,[],[[\"x\"], null,[0],[\"x\"]]]";
JArray array = JArray.Parse(json);
// Keep first 3 elements, remove the rest
int max = array.Count;
for (int i = 0; i < max - 3; i++)
{
JToken elem = array[3];
array.Remove(elem);
}
json = array.ToString(Newtonsoft.Json.Formatting.None);
Console.WriteLine(json);