Deep count for object properties - c#

I'd like to count the number of properties within an object.
I have found several solutions but none really counts the child\nested propertie.
For instance, I have the following JSON object.
{
"id": "259a36d2-3852-425f-a70c-3f9477753210",
"name": "foo",
"type": "na",
"text": "ABC.pdf",
"left": 333,
"top": 130,
"w": 134,
"h": 34,
"customData": {
"A": "fa6css4ec8-8ffb-55bca4dde06a",
"name": "SDF.pdf",
"IsExists": false,
"PNumber": 1,
}
}
When trying the following I'm getting the result of 9, while I'm expecting 12 (which is the count of the entire properties).
JObject sourceJObject = JsonConvert.DeserializeObject<JObject>(json);
var res= sourceJObject.Count;
I'll be happy for assistant.

JObject.Count is documented as returning the count of child tokens - not all descendant tokens. So while you may want 12, you shouldn't really expect 12.
If you want the count of all descendants, you can use sourceJObject.Descendants().Count(). However, that will give you 13 rather than 12, because customData itself is a token.
If you want "all descendant properties where the value isn't an object" you can use OfType and Where, as shown in the code below. You should probably think about what you want to do with arrays though...
using Newtonsoft.Json.Linq;
string json = File.ReadAllText("test.json");
JObject jobject = JObject.Parse(json);
var properties = jobject.Descendants()
.OfType<JProperty>()
.Where(prop => prop.Value is not JObject);
Console.WriteLine($"Count: {properties.Count()}");
foreach (var property in properties)
{
Console.WriteLine($"{property.Name} = {property.Value}");
}
Output:
Count: 12
id = 259a36d2-3852-425f-a70c-3f9477753210
name = foo
type = na
text = ABC.pdf
left = 333
top = 130
w = 134
h = 34
A = fa6css4ec8-8ffb-55bca4dde06a
name = SDF.pdf
IsExists = False
PNumber = 1

try this
var obj=JObject.Parse(json);
var count=0;
CountProps(obj, ref count); // count=12
public void CountProps(JToken obj, ref int count)
{
if (obj is JObject)
foreach (var property in ((JObject)obj).Properties())
{
if (property.Value is JValue) count++;
if (property.Value is JObject) CountProps(property.Value, ref count);
if (property.Value is JArray) { count++; CountProps(property.Value, ref count); }
}
if (obj.GetType().Name == "JArray")
foreach (var property in ((JArray)obj))
if (property is JObject || property is JArray) CountProps(property, ref count);
}

Related

Update all keys with a specific prefix in Newtonsoft JObject

How can I update all keys with a given prefix at all levels in a JObject with a specific value? e.g.
{
"nameOne": "dave",
"age": 23,
"foo": {
"nameTwo": "pete",
"age": 56
}
}
How can I update nameOne and nameTwo (name*) to "chris"?
if it goes no deeper than in your example you can try this
_settings = JObject.Parse(File.ReadAllText(SettingsFile));
_settings["nameOne"]="cris";
_settings["foo"]["nameTwo"]="cris";
or if you need some search, try this
var searchString = "name";
var newValue = "cris";
foreach (var property in _settings)
{
var key = property.Key;
if (property.GetType().Name != "JObject")
{
if (key.Contains(searchString)) _settings[key] = newValue;
}
else
{
JObject prop = _settings[key] as JObject;
foreach (var nestedProperty in prop)
{
var nestedKey = nestedProperty.Key;
if (nestedKey.Contains(searchString)) prop[nestedKey] = newValue;
}
}
}
instead of Contains you can use StartWith or EndsWith as well
it was tested in visual studio
{
"nameOne": "cris",
"age": 23,
"foo": {
"nameTwo": "cris",
"age": 56
}
}

Iterate IDictionary<string, string> with dynamic nested JSON as value in C#

My application receives Kafka messages that contain a Dictionary<string,string> as one of the properties, and its values could be a nested (however dynamic) json string, and I need to iterate through this unknown json. I am struggling to find a logic and even the best data structure to do this iteration.
Examples of the dictionary (mocked data):
//could have complex nested json string as value
"reward":"{
'xp':'200',
'gp':'150',
'loot':'{
'item':'sword',
'rarity': 'low'
}'
}",
"achievement":"win_match"
// while other messages might be simple
"type":"generic_xp",
"percent":"100",
"status":"complete"
Serialized version of a real message:
"{\"player_stats\":\"{\\\"assist\\\":0,\\\"deaths\\\":0,\\\"kills\\\":0,\\\"team_index\\\":2}\",\"round_attr\":\"{\\\"max_player_count\\\":4,\\\"rdur\\\":0,\\\"round\\\":1,\\\"team_player_count\\\":{\\\"team_1\\\":1,\\\"team_2\\\":0},\\\"team_score\\\":0}\",\"custom\":\"{\\\"armor\\\":\\\"armor_pickup_lv2\\\",\\\"balance\\\":550,\\\"helmet\\\":\\\"helmet_pickup_lv2\\\",\\\"misc\\\":[{\\\"count\\\":48,\\\"item_id\\\":\\\"shotgun\\\"},{\\\"count\\\":120,\\\"item_id\\\":\\\"bullet\\\"},{\\\"count\\\":2,\\\"item_id\\\":\\\"health_pickup_combo_small\\\"},{\\\"count\\\":2,\\\"item_id\\\":\\\"health_pickup_health_small\\\"}],\\\"weapon_1\\\":\\\"mp_weapon_semipistol\\\",\\\"weapon_2\\\":\\\"mp_weapon_shotgun_pistol\\\"}\",\"gdur\":\"0\"}"
To complicate even more
Create a model class is not an option because this json is completely dynamic
Flatting the dictionary is not possible because the json may have duplicated key names, but under different hierarchy
I cant request to change the Kafka message
What I am trying to do
The end user will define rules that I need to check if I find a match. For instance, a rule could be reward.xp == 200 or reward.loot.rarity == high or status == complete. These rules will be defined by the user so it cant be hardcoded, however I can decide with data structure to use to save them. So for each Kafka message, I have to iterate through that dictionary and try to find a match with the rules.
What I have tried
I ve tried JsonConvert.Deserialize to object, dynamic, ExpandoObject and none could handle the nested json hierarchy. They just got the 1st level correct. Same result with JObject.Parse as well.
Parse the JSON using whatever parser you like (I used Newtonsoft.Json).
Then recursively visit the hierarchy and copy each property to a flat list using the full path to each property value as a key. You can then iterate that flat list.
Edit: Comment requested supporting arrays, so this version does.
https://dotnetfiddle.net/6ykHT0
using System;
using Newtonsoft.Json.Linq;
using System.Linq;
using System.Collections.Generic;
public class Program
{
public static void Main()
{
string json = #"{
'reward': {
'xp': '200',
'gp': '150',
'loot': {
'item': 'sword',
'rarity': 'low',
'blah': {
'socks': 5
}
},
'arrayofint': [1,2,3,4],
'arrayofobj': [
{
'foo': 'bar',
'stuff': ['omg!', 'what?!']
},
{
'foo': 'baz',
'stuff': ['a', 'b']
}
],
'arrayofarray': [
[1,2,3],
[4,5,6]
],
'arrayofheterogenousjunk': [
'a',
2,
{ 'objprop': 1 },
['staahp!']
]
},
'achievement': 'win_match'
}";
JObject data = JObject.Parse(json);
IList<string> nodes = flattenJSON(data);
Console.WriteLine(string.Join(Environment.NewLine, nodes));
}
private static IList<string> flattenJSON(JToken token)
{
return _flattenJSON(token, new List<string>());
}
private static IList<string> _flattenJSON(JToken token, List<string> path)
{
var output = new List<string>();
if (token.Type == JTokenType.Object)
{
// Output the object's child properties
output.AddRange(token.Children().SelectMany(x => _flattenJSON(x, path)));
}
else if (token.Type == JTokenType.Array)
{
// Output each array element
var arrayIndex = 0;
foreach (var child in token.Children())
{
// Append the array index to the end of the last path segment - e.g. someProperty[n]
var newPath = new List<string>(path);
newPath[newPath.Count - 1] += "[" + arrayIndex++ + "]";
output.AddRange(_flattenJSON(child, newPath));
}
}
else if (token.Type == JTokenType.Property)
{
var prop = token as JProperty;
// Insert the property name into the path
output.AddRange(_flattenJSON(prop.Value, new List<string>(path) { prop.Name }));
}
else
{
// Join the path segments delimited with periods, followed by the literal value
output.Add(string.Join(".", path) + " = " + token.ToString());
}
return output;
}
}
Output:
reward.xp = 200
reward.gp = 150
reward.loot.item = sword
reward.loot.rarity = low
reward.loot.blah.socks = 5
reward.arrayofint[0] = 1
reward.arrayofint[1] = 2
reward.arrayofint[2] = 3
reward.arrayofint[3] = 4
reward.arrayofobj[0].foo = bar
reward.arrayofobj[0].stuff[0] = omg!
reward.arrayofobj[0].stuff[1] = what?!
reward.arrayofobj[1].foo = baz
reward.arrayofobj[1].stuff[0] = a
reward.arrayofobj[1].stuff[1] = b
reward.arrayofarray[0][0] = 1
reward.arrayofarray[0][1] = 2
reward.arrayofarray[0][2] = 3
reward.arrayofarray[1][0] = 4
reward.arrayofarray[1][1] = 5
reward.arrayofarray[1][2] = 6
reward.arrayofheterogenousjunk[0] = a
reward.arrayofheterogenousjunk[1] = 2
reward.arrayofheterogenousjunk[2].objprop = 1
reward.arrayofheterogenousjunk[3][0] = staahp!
achievement = win_match
PREVIOUS VERSION (NO ARRAY SUPPORT)
This doesn't properly support arrays - it will output the contents of a property that is an array as the raw JSON - i.e. it won't traverse into the array.
https://dotnetfiddle.net/yZbwul
public static void Main()
{
string json = #"{
'reward': {
'xp': '200',
'gp': '150',
'loot': {
'item': 'sword',
'rarity': 'low',
'blah': {
'socks': 5
}
}
},
'achievement': 'win_match'
}";
JObject data = JObject.Parse(json);
IList<string> nodes = flattenJSON(data, new List<string>());
Console.WriteLine(string.Join(Environment.NewLine, nodes));
}
private static IList<string> flattenJSON(JObject obj, IList<string> path)
{
var output = new List<string>();
foreach (var prop in obj.Properties())
{
if (prop.Value.Type == JTokenType.Object)
{
output.AddRange(flattenJSON(prop.Value as JObject, new List<string>(path){prop.Name}));
}
else
{
var s = string.Join(".", new List<string>(path) { prop.Name }) + " = " + prop.Value.ToString();
output.Add(s);
}
}
return output;
}
Output:
reward.xp = 200
reward.gp = 150
reward.loot.item = sword
reward.loot.rarity = low
reward.loot.blah.socks = 5
achievement = win_match

iterate through JSON?

Can I Iterate (recursive) through this JSON?
My Problem are not the JObject rather the JArray.
This is a multi-nested JSON that I would like to go through and from which I would like to read the key and value separately.
{
"DataValid": 1,
"Identifier": "865263040394502",
"RecordType": "D",
"TypeD_data": {
"tStat": {
"lDistance": 545190,
"lUpTimeGps": 21074,
"lUpTimeGsm": 34700,
"lUpTimeMCU": 127387,
"lUpTimeSys": 4727445,
"lUpTimeInMotion": 66343
},
"header": {
"info": 16797,
"tTime": 1597039293,
"reason": 4398046511136,
"version": 1
},
"tAcc_bas": {
"sXRaw": 65,
"sYRaw": 67,
"sZRaw": 983,
"Status": 1,
"sMagnitude": 66
},
"tAcc_ext": null,
"tGeo_bas": {
"polycount": 0
},
"tGeo_ext": null,
"tGps_bas": {
"Status": 11,
"sSpeed": 79,
"sCourse": 321,
"fLatitude": 48.59036,
"sAltitude": 474,
"fLongitude": 11.56852
},
"tGsm_bas": {
"ta": 0,
"mcc": 0,
"mnc": 0,
"Status": 2,
"cellcount": 0
},
"tGsm_ext": null,
"tTele_ext": null,
"tTele_int": {
"Status": 16,
"sTmpMCU": 2061,
"sBatLevel": 50,
"sBatVoltage": 3346,
"sExtVoltage": 0
},
"tTele_chain": null
},
"DataIdentifier": "Pos",
"Timestamp_Received": 1597039264
}
above there is the JSON I want to iterate through.
My Code:
public void SetValue(JObject value, string valueName = "")
{
foreach (var p in value)
{
if (p.Value is JObject)
{
SetValue((JObject)p.Value, valueName + "/" + p.Key);
}
}
}
above there is my Code.
Thank you in advance for the help.
public List<string> GetFieldNames(dynamic input)
{
List<string> fieldNames = new List<string>();
try
{
// Deserialize the input json string to an object
input = Newtonsoft.Json.JsonConvert.DeserializeObject(input);
// Json Object could either contain an array or an object or just values
// For the field names, navigate to the root or the first element
input = input.Root ?? input.First ?? input;
if (input != null)
{
// Get to the first element in the array
bool isArray = true;
while (isArray)
{
input = input.First ?? input;
if (input.GetType() == typeof(Newtonsoft.Json.Linq.JObject) ||
input.GetType() == typeof(Newtonsoft.Json.Linq.JValue) ||
input == null)
isArray = false;
}
// check if the object is of type JObject.
// If yes, read the properties of that JObject
if (input.GetType() == typeof(Newtonsoft.Json.Linq.JObject))
{
// Create JObject from object
Newtonsoft.Json.Linq.JObject inputJson =
Newtonsoft.Json.Linq.JObject.FromObject(input);
// Read Properties
var properties = inputJson.Properties();
// Loop through all the properties of that JObject
foreach (var property in properties)
{
// Check if there are any sub-fields (nested)
// i.e. the value of any field is another JObject or another JArray
if (property.Value.GetType() == typeof(Newtonsoft.Json.Linq.JObject) ||
property.Value.GetType() == typeof(Newtonsoft.Json.Linq.JArray))
{
// If yes, enter the recursive loop to extract sub-field names
var subFields = GetFieldNames(property.Value.ToString());
if (subFields != null && subFields.Count() > 0)
{
// join sub-field names with field name
//(e.g. Field1.SubField1, Field1.SubField2, etc.)
fieldNames.AddRange(
subFields
.Select(n =>
string.IsNullOrEmpty(n) ? property.Name :
string.Format("{0}.{1}", property.Name, n)));
}
}
else
{
// If there are no sub-fields, the property name is the field name
fieldNames.Add(property.Name + " : " + (string)property.Value<JProperty>());
}
}
}
else
if (input.GetType() == typeof(Newtonsoft.Json.Linq.JValue))
{
// for direct values, there is no field name
fieldNames.Add(string.Empty);
}
}
}
catch
{
throw;
}
return fieldNames;
}

Build a Property List from a JSON Object c#

I want to build the property list including property path of a json object.
I don't know the structure of the json or the keys that might be present. I'm after the keys at all levels (not the values of those keys).
{
"Primitive_1": "T1",
"Object_L1": {
"Object_L2": {
"Object_L3": {
"Object_L4": {
"Object_L5": {
"Object_L6": {
"Array_L7": [
{
"asdasdas": "SampleText1",
"WIDTH": "Width2"
},
{
"gh45gdfg": "SampleText2",
"WIDTH": "Width"
}
],
"12836hasvdkl": "SampleText3",
"WIDTH": "Width"
}
}
},
"712bedfabsmdo98": "SampleText4",
"WIDTH": "Width"
}
},
"ALIAS_ID": 1
},
"Primitive_2": "T2",
"Primitive_3": "T3",
"Primitive_4": "T4"
}
Desired output:
.Primitive_1 .Object_L1.Object_L2.Object_L3.Object_L4.Object_L5.Object_L6.Array_L7.0.asdasdas
.Object_L1.Object_L2.Object_L3.Object_L4.Object_L5.Object_L6.Array_L7.0.WIDTH
.Object_L1.Object_L2.Object_L3.Object_L4.Object_L5.Object_L6.Array_L7.1.gh45gdfg
.Object_L1.Object_L2.Object_L3.Object_L4.Object_L5.Object_L6.Array_L7.1.WIDTH
.Object_L1.Object_L2.Object_L3.Object_L4.Object_L5.Object_L6.12836hasvdkl
.Object_L1.Object_L2.Object_L3.Object_L4.Object_L5.Object_L6.WIDTH
.Object_L1.Object_L2.Object_L3.712bedfabsmdo98
.Object_L1.Object_L2.Object_L3.WIDTH
.Object_L1.ALIAS_ID
.Primitive_2
.Primitive_3
.Primitive_4
Having looked around I've gotten as far as the root nodes of the object. See fiddle (https://dotnetfiddle.net/wIl1Qw)
This seems to be relatively simple in JS (http://jsfiddle.net/alteraki/bt3zc1wt/) I've already reviewed several responses and I can't find a response in c# that solves this problem without knowing the keys in use (which I don't know)
Any help would be much appreciated.
Tree traversal algorithms are almost always recursive in nature.
As such, the following function does what you want:
private static IEnumerable<string> GetMembers(JToken jToken)
{
var members = new List<string>();
if (jToken is JObject)
{
var jObject = (JObject)jToken;
foreach (var prop in jObject.Properties())
{
if (prop.Value is JValue)
{
members.Add(prop.Name);
}
else
{
members.AddRange(GetMembers(prop.Value).Select(member => prop.Name + "." + member));
}
}
}
else if (jToken is JArray)
{
var jArray = (JArray)jToken;
for (var i = 0; i < jArray.Count; i++)
{
var token = jArray[i];
members.AddRange(GetMembers(token).Select(member => i + "." + member));
}
}
return members;
}
An example of the code running is available here.

getting value of children

My simple json data as below
string _JsonData = #" {
"tm":{
"1":{
"pl":{
"11":{
"foo":"2"
},
"902":{
"foo":"70"
}
}
}
}";
I can get value of pl children's foo values (such as 2 and 70) as below code
JObject _JObject = JObject.Parse(_JsonData);
foreach (JToken _JTokenCurrent in _JObject["tm"]["1"]["pl"].Children())
{
MessageBox.Show(_JTokenCurrent["foo"].ToString());
}
So how can i get value of pl children's property values (such as 11 and 902)?
Thank you in advance.
OK I have solved as below;
JObject _JObject = JObject.Parse(_JsonData);
foreach (JToken _JTokenCurrent in _JObject["tm"]["1"]["pl"].Children())
{
// get values such as 11 and 902
JProperty _JTokenCurrentName = (JProperty)_JTokenCurrent;
MessageBox.Show(_JTokenCurrentName.Name);
/// get values such as 2 and 70
MessageBox.Show(_JTokenCurrent["foo"].ToString());
}
Not tested!
JObject _JObject = JObject.Parse(_JsonData);
foreach (JToken _JTokenCurrent in _JObject["tm"]["1"]["pl"].Children())
{
// Should be your 11 and 902
MessageBox.Show(_JTokenCurrent.Children().ToString());
// Should be your 2 nad 70
MessageBox.Show(_JTokenCurrent["foo"].ToString());
}

Categories