Query JArray using SelectTokens and a contains regex - c#

I am trying to write a method that will take the following JSON structure below and return the path proerty for all of these where path contains a string. So in this case, if I called GetPath(json, "User1") I would like to get ["User1\path1", "User1\path2"]
[
{
"id":90BCV,
"name":"Path 1",
"path":"User1\\path 1"
},
{
"id":90BC,
"name":"Path 2",
"path":"User1\\path 2"
},
{
"id":91AB,
"name":"Path 3",
"path":"User2\\path 3"
}
]
public static List<string> GetPath(string json, string key)
{
JArray reader = JArray.Parse(json);
List<JToken> paths = reader.SelectTokens("$.[?(#.path=~#'" + key + "')]").ToList();
List<string> returnList = new List<string>();
foreach (JToken path in paths)
{
returnList.Add((string)path["path"]);
}
return returnList;
}
I have tried multiple different regex approaches but I just can not seem to get this query right. Most of the times I just end up getting an empty list or an invalid regex error.

That works
static string json = #"
[
{
""id"":""90BCV"",
""name"":""Path 1"",
""path"":""User1\\path 1""
},
{
""id"":""90BC"",
""name"":""Path 2"",
""path"":""User1path 2""
},
{
""id"":""91AB"",
""name"":""Path 3"",
""path"":""User2\\path 3""
}
]
";
static void Main(string[] args)
{
string key = "User2\\\\path 3";
JArray reader = JArray.Parse(json);
List<JToken> paths = reader.SelectTokens("$.[?(#.path=='" + key + "')]").ToList();
}
few thing are there
- ==, not just =, cause JPath uses in-code compiler, so you follow C# convension.
- for the same reason, it you need \\ to represent \

By the way, if you really want to do Regular Expression way, you almost had it right:
List<JToken> paths = reader.SelectTokens("$.[?(#.path=~/^" + key + "$/)]").ToList();

I don't know why you would want to go with this approach. I would rather use .ToString() and then use .Contains inside the foreach loop.
JArray reader = JArray.Parse(json);
List<string> returnList = new List<string>();
foreach(var token in reader){
string path = token["path"].ToString();
if(path.Contains(key)){
returnList.Add(path);
}
}

Related

C# deserialize selected properties in json

Given the following JSON
{
"enabled": true,
"name": "Name",
"description": "Test",
"rules": [
{
"propA": "a",
"propB": "b"
}
]
}
Is it possible in C# to deserialize on selected properties based on an input list:
var propertiesToInclude = new List<string> { "description", "rules.PropA" };
The example json is a simplified example, the real one can contain hundred of properties. The use case is to only return the fields that matches the input list in a dynamic or anonymous object and discard the other properties.
using Newtonsoft.Json.Linq;
var propertiesToInclude = new List<string> { "description", "rules.PropA" };
var splitted = propertiesToInclude.SelectMany(x => x.Split('.'));
string text = File.ReadAllText("test.json");
var json = JToken.Parse(text);
Process(json);
Console.WriteLine(json);
void Process(JToken token)
{
if (token is JObject jObject)
{
jObject.Properties()
.Where(x => !splitted.Contains(x.Name, StringComparer.OrdinalIgnoreCase))
.ToList()
.ForEach(x => x.Remove());
foreach (var x in jObject)
Process(x.Value);
}
else if (token is JArray jArray)
{
foreach (var x in jArray)
Process(x);
}
}
This code on the data shown will give the desired result.
The output is a JToken object containing the desired properties.
However, I used a simple search for all occurrences of names in a splited array. This will give false positives if, for example, the root object contains the propA property or the object in the array contains the description property.
To avoid this, you need to compare the JToken.Path property with the propertiesToInclude elements, taking into account the depth.

Flexibly edit json values in C#

I've been trying to insert values from one json file into another. These structures have mostly been decided on, so I want to try and make them work as they are.
The problem i've been running into is that the provided paths may not always have the same depth and thus the coded used to insert values into that provided path needs to be flexible enough to deal with that.
These are the json files:
this json holds values, the paths to where those values should be placed in the other json file and when applicable, the index.
[
{
"path": "x_axis.data",
"value": "['','2005','2010','2015','2021','2026']",
"index": null
},
{
"path": "series.data",
"value": "[77.0,70.0,74.0,0.0,0.0,0.0]",
"index": 0
},
{
"path": "series.data",
"value": "[0.0,0.0,0.0,99.0,100.0,100.0]",
"index": 1
}
]
This json is a template structure that will be reused. it' based on the Echarts documentation, since we use that library to show charts.
{
"tooltip": {
"trigger": "axis",
"axisPointer": {
"type": "shadow"
}
},
"legend": {
"data": [ "One", "Two" ]
},
"grid": {
"left": "3%",
"right": "4%",
"bottom": "3%",
"containLabel": true
},
"xAxis": [
{
"type": "category",
"data": []
}
],
"yAxis": [
{
"type": "value"
}
],
"series": [
{
"name": "One%",
"type": "bar",
"stack": "Stack 1",
"emphasis": {
"focus": "series"
},
"itemStyle": {
"color": "blue"
},
"data": []
},
{
"name": "Two%",
"type": "bar",
"stack": "Stack 1",
"emphasis": {
"focus": "series"
},
"itemStyle": {
"color": "red"
},
"data": []
}
]
}
Currently my method converts the path into a string array with text formatting that matches the names in the template json. I am however unsure how to use it or whether it was the right choice.
Here is my code:
public static string GenerateChart(string templateString, string replacementValuesString)
{
string result = "";
dynamic templateJson = JObject.Parse(templateString);
dynamic replacementJson = JArray.Parse(replacementValuesString);
foreach (var item in replacementJson)
{
string[] pathArr = CreatePathArray(item);
//check for index
if (item.index != null)
{
//TODO: Implement code that handles the index
templateJson.path = item.value;
}
else
{
templateJson.path = item.value;
}
}
result = templateJson.ToString();
return result;
}
I used Newtonsoft to parse the json. My team decided against using models to deserialise the Json because they wanted to be able to only edit the json files for maintenance of the program.
My question to anyone willing to help is how would you go about solving this problem?
If I've failed to explain bits properly please let me know so I can clarify.
Thank you for your help!
Edit 1: I forgot to mention how the path is split into the array. It's split at the period, so "x_axis.data" becomes "["xAxis", "data"]".
Thanks to E. Shcherbo for helping me find a solution
I ended up modifying the code he suggested to fit my project as a few minor things were causing issues. Here's the code I ended up using and will now continue to polish:
var replacementJson = JArray.Parse(replacementValuesString);
JToken templateJson = JObject.Parse(templateString);
foreach (var item in replacementJson)
{
string[] pathArr = CreatePathArray(item);
//The method using ^1 provided by E. Shcherbo didn't work for me here.
//I'm guessing it's because I'm working in an old version of dotnetframework.
var valuePropName = pathArr[pathArr.Length - 1];
//Using this loop to "dive deeply" into the template json as E. put it, we find the correct path and then modify it.
for (int i = 0; i < pathArr.Length - 1; i++)
{
var pathKey = pathArr[i];
templateJson = templateJson[pathKey];
if (templateJson == null)
{
Console.WriteLine("Template path given is invalid.");
break;
}
}
// if my item contains a value for index, it will be used. Otherwise it's safe to assume we're not dealing with an array.
if (item["index"] != null)
{
UpdateValueInTemplate(templateJson, item["index"], valuePropName, item["value"]);
}
else
{
UpdateValueInTemplate(templateJson, valuePropName, item["value"]);
}
templateJson = templateJson.Root;
}
First of all you probably don't need dynamic type here, because you know types of your objects at compile time JObject, JArray, JToken, so this is much safer to use them (you can go with var).
I'll describe two approaches I see here (both of them reuse the UpdateValueInTemplate method).
Note: be careful with different edge cases which might be possible depending on what you can and can't assume about your data.
Giving your approach with an array which represents the path, you can just iterate through the array and go deeply and deeply in your templateJson. Something like this
JToken templateJson = JObject.Parse(templateString);
var valuePropName = pathArr[^1]; // ^1 means first from the end
for (int i = 0; i < pathArr.Length - 1; i++)
{
var pathKey = pathArr[i];
templateJson = templateJson[pathKey];
if (templateJson is null)
// The path is wrong. Do whatever validation is expected
}
UpdateValueInTemplate(templateJson, valuePropName, item["value"]);
You can also pass your path from replacementJson (without the last property to get an object where the last property is rather than value of the property) into the SelectToken method. Something like this:
var path = item["path"].ToString();
var lastPathSplitter = item.Path.LastIndexOf('.');
var pathWithoutLastProperty = path.Substring(0, lastPathSplitter);
var valPropName = item.Path.Substring(lastPathSplitter + 1);
var jsonToUpdate = templateJson.SelectToken(pathWithoutLastProperty);
if (jsonToUpdate is null)
// The path is wrong. Do whatever validation is expected
UpdateValueInTemplate(jsonToUpdate, valPropName, item["value"]);
Then the UpdateValueInTemplate method can look like this:
void UpdateValueInTemplate(JToken tokenToUpdate, string valPropName, JToken value) {
tokenToUpdate[valPropName] = value;
}
You can also reduce manipulations with your path if you pass the entire path to the SelectToken and then will use yourToken.Parent.Parent[valPropName] = value to update the json.
UPDATE
I didn't notice that you also want to handle cases where the first property of the path can point to the array. So when this is the case it can be handled like this:
JToken templateJson = JObject.Parse(templateString);
var valuePropName = pathArr[^1]; // ^1 means first from the end
for (int i = 0; i < pathArr.Length - 1; i++)
{
var pathKey = pathArr[i];
templateJson = templateJson[pathKey];
if (templateJson is null)
{
// The path is wrong. Do whatever validation is expected
}
else if (templateJson.Type == JTokenType.Array)
{
int index = item["index"].ToObject<int>(); // you may want to validate the index presence and format
templateJson = templateJson[index];
}
}
For approach with SelectToken you can either change your replacement json to match the format SelectToken understands or create a new path based on the index when it's not null:
var firstPropSplitter = path.IndexOf('.');
var firstProperty = path.Substring(0, firstPropSplitter);
var restPath = path.Substring(firstPropSplitter + 1);
var index = item["index"];
var newPath = $"{firstProperty}[{index}].{restPath}";

Read json value using C# without using Property

I need to read the value of testCase.name for id 100000 from following JSON response string.
response = {
"count": 2,
"value": [
{
"id": 100000,
"project": {
"id": "aaaa-bbbb-cccc-dddd",
"name": "MyTestProject",
"url": "https://dev.azure.com/MyOrg/_apis/projects/MyTestProject"
},
"testCase": { "name": "GetProjectTeamDetails" }
},
{
"id": 100001,
"project": {
"id": "aaaa-bbbb-cccc-dddd",
"name": "MyTestProject",
"url": "https://dev.azure.com/MyOrg/_apis/projects/MyTestProject"
},
"testCase": { "name": "QueueBuild" }
}
]
}
I've tried the following codes but could not achieve:
Try1:
JObject o = JObject.Parse(response)
string testCaseName= (string)o["values"][0];
Try2:
JObject jObject = JObject.Parse(response);
string displayName = (string)jObject.SelectToken("testCase.name");
You could use
var jo = JObject.Parse(json);
var testCaseName = (string)jo.SelectToken($"$.value[?(#id=={idToSearch})].testCase.name");
Update
Base on your question in comment, you could find the ID with test case name using
jo.SelectTokens($"$.value[?(#testCase.name=='{nameToSearch}')].id")
Please note in this case, you would need to use .SelectTokens as there is chances of duplicates as give in the example in OP. You could get all the results or the first based on your requirement.
var idList = jo.SelectTokens($"$.value[?(#testCase.name=='{nameToSearch}')].id")
.Select(x=> long.Parse(x.ToString()));
var onlyFirst = (long)jo.SelectTokens($"$.value[?(#testCase.name=='{nameToSearch}')].id")
.First();
You need to step through hierarchy.
JObject jObject = JObject.Parse(jsonString);
var testCaseName = jObject.SelectToken("value[0].testCase.name").ToString();
Another approach is using dynamic keyword:
dynamic jt = JToken.Parse(response);
IEnumerable<dynamic> values = jt.value;
string name = values.FirstOrDefault(v => v.id == 100000)?.testCase.name;
The other answers have shown using SelectToken, which is fine - if you don't want to do that, you can still do it by accessing one property at a time:
using System;
using System.IO;
using Newtonsoft.Json.Linq;
class Test
{
static void Main()
{
string json = File.ReadAllText("test.json");
JObject obj = JObject.Parse(json);
string testCase = (string) obj["value"][0]["testCase"]["name"];
Console.WriteLine(testCase);
}
}
In your Try1 you're using values instead of value, and you're stopping at the array - you're not asking for the testCase property, or the name property within that.
In your Try2 you're doing exactly the opposite - you're looking for testCase.name without selecting an element within the value array first.
As one third way, you could use dynamic typing:
using System;
using System.IO;
using Newtonsoft.Json.Linq;
class Test
{
static void Main()
{
string json = File.ReadAllText("test.json");
dynamic obj = JObject.Parse(json);
string testCase = obj.value[0].testCase.name;
Console.WriteLine(testCase);
}
}

Why can I not parse Neo4jClient's query result (type string) into a Dictionary<string, string> using JsonConvert in C#

I am trying to parse Neo4jClient's query result of paths (of type string) using JsonConvert.
I was able to parse the query result for nodes using this method:
var _gClient = new GraphClient(new Uri("http://localhost:7474/db/data"));
_gClient.Connect();
var result = _gClient.Cypher
.Match("(n)")
.Return(n => n.As<string>())
.Results.ToList();
result.ForEach(n =>
{
var dict = JsonConvert.DeserializeObject<Dictionary<string, string>>(n);
Console.WriteLine("Keys: " + String.Join(",", dict.Keys));
Console.WriteLine("Values: " + String.Join(",", dict.Values));
});
However, when I tried to do the same with the query result of paths, JsonConvert's DeserializeObject method can't do the same thing:
var _gClient = new GraphClient(new Uri("http://localhost:7474/db/data"));
_gClient.Connect();
var result = _gClient.Cypher
.Match("p=(a)-[:ACTED_IN]->(m:Movie {title:'The Matrix'})")
.Return(p => new
{
Nodes = Return.As<IEnumerable<string>>("EXTRACT(p in nodes(p) | p)"),
Relationships = Return.As<IEnumerable<string>>("EXTRACT(p in relationships(p) | p)")
})
.Results.ToList();
foreach (var n in result)
{
foreach (var s in n.Nodes)
{
JsonConvert.DeserializeObject<Dictionary<string, string>>(s);
}
}
The error was Unexpected character encountered while parsing value: {. Path 'extensions', line 2, position 17.
This is part of the string to be parsed:
{
"extensions": {},
"metadata": {
"id": 8,
"labels": [
"Person"
]
},
Does that mean Json can't deserialize empty braces? If so is there any other way I can parse this huge structure out?
The problem is that you're trying to deserialize into a Dictionary<string,string> but your JSON isn't a Dictionary<string,string> the error about the unexpected { is because for it to be a Dictionary<string,string> the JSON would look like:
{
"Key1" : "Value1",
"Key2" : "Value2"
}
The { on both extensions and metadata imply that there is an object there, not just a string. To that end you can deserialize it using something like:
JsonConvert.DeserializeObject<Dictionary<string, object>>(s)
But if you want to access the properties, you may as well do:
JsonConvert.DeserializeObject<Dictionary<string, dynamic>>(s)

error of reading Json string in C# by JsonConvert

I need to read data from a Json string in C#.
The Json string is like:
{
"data_level":{
"performance":{
"#value":"1000",
"#size":"10",
},
"points":{
"#type":"profit",
"tier":{
"#below":"80",
"#above":"100"
},
"kids":[
{
"#kid":"150"
},
{
"#kid":"200"
}
]
}
}
My C# code:
var my_dic = JsonConvert.DeserializeObject<Dictionary<string, dynamic>>(json_string);
var my_data = my_dic["data_level"]
string v = my_data["performance"]["#size"];
For "kids", I have two child "kid" have the same name but differne value. How to get all of them instead of only the one last read ?
Any help would be appreciated.
You should leave out the last [0] .
For the updated question:
my_children = my_dic["points"]["kids"];
foreach (KeyValuePair<string, int> pair in my_children)
{
Console.WriteLine(pair.Key, pair.Value["#kid"]);
}
This should work...

Categories

Resources