Find and retrieve keyed value from anywhere in JSON string - c#

I'm retrieving JSON which may vary in content items and want to find values associated with particular keys, e.g. in this case "geo_latitude" and "geo_longitude". For clarity, I'm only including those two items in this JSON sample. (It's typically much longer and in no particular order).
[
{
"id": 524616,
"key": "geo_latitude",
"value": "36.1069652"
},
{
"id": 524617,
"key": "geo_longitude",
"value": "-112.1129972"
}
]
This was the best I was able to come up with to parse the JSON. Is there a better way?
var metadataObj = JArray.Parse(postMetadataJsonStr);
var latContainer = metadataObj.Descendants()
.OfType<JObject>()
.Where(x => x["key"] != null &&
x["key"].Value<string>() == "geo_latitude").Select(y => y["value"]);
var latTokenJValue = latContainer.FirstOrDefault();
if (latTokenJValue == null) return;
var latitude = latTokenJValue.ToString();

I would convert your json to a dictionary
var metadataObj = JArray.Parse(postMetadataJsonStr);
var dict = metadataObj.ToDictionary(x => (string)x["key"], x => (double)x["value"]);
Console.WriteLine(dict["geo_latitude"]);

Related

How to extract a field from JSON

Using NewtonSoft, I am able to parse JSON into this:
var jtoken = JObject.Parse(stringStuff);
Console.WriteLine(jtoken.ToString());
gives this:
{
"data": {
"user": {
"email": "john#doe.com",
"external-id": "af36-e9fddecdb755"
},
"session-token": "G_uJNNxmLNtxcmM2ilB6P_dN67p9XXk3-yn8peUBi7k3xH50Ch7MUQ+C"
},
"context": "/sessions"
}
However, if I try to get the field "session-token" like this,
var token = jtoken.Value<string>("session-token");
token is null. Not sure what I am doing wrong?
a "session-token" property is nested inside of a "data" property
string token = (string) jtoken["data"]["session-token"]; // G_uJNNxmLNtxcmM2ilB6P_dN67p9XXk3-yn8peUBi7k3xH50Ch7MUQ+C
You need to run through the descendants of your root object, and find the JProperty with the name you want -
var token = root.Descendants()
.OfType<JProperty>()
.Where(x => x.Name == "session-token")
.FirstOrDefault()
?.Value
?.Value<string>();
if(token != null)
{
//token is "G_uJNNxmLNtxcmM2ilB6P_dN67p9XXk3-yn8peUBi7k3xH50Ch7MUQ+C"
}
It's a little awkward, the first .Value returns the JProperty's JToken, then the second .Value gets the string value.

Get only one variable from json response

I make a POST request and I get back json response with a lot of transactions in it like:
"Transactions": [
{
"ID": "1",
"Name" : "name",
"Code": "123aaa"
},
{
"ID": "2",
"Name" : "name1",
"Code": "12345bbb"
}
]
Is there any simple way to get one variable (for example first "Code" value)? Without entity classes etc?
I've tried this
var content = new StringContent(request, Encoding.UTF8, "application/json");
var response = await HttpClient.PostAsync(postURL, content);
var text = await response.Content.ReadAsStringAsync();
var code = JObject.Parse(text).Children().First().Values<string>("Code").First();
But it didn't work:
System.InvalidOperationException : Cannot access child value on Newtonsoft.Json.Linq.JValue.
Assuming that provided json is actually an object (i.e. string in question is wrapped in curly brackets) - you can use json path:
var code = JObject.Parse(text).SelectToken("$.Transactions[0].Code").ToString(); // results in "123aaa"
If you are keen on using LINQ to JSON you can do something like this:
var code = JObject.Parse(text)
.Descendants()
.OfType<JProperty>()
.Where(p => p.Name == "Code")
.Select(p => p.Value.ToString())
.FirstOrDefault();
But the easiest I think would be to use indexers:
var code = JObject.Parse(text)["Transactions"][0]["Code"].ToString();

Find item(s) in a nested dictionary by secondary key

This is my problem: I have a nested dictionary (primary keys: orderIds, secondary keys: productIds).
My scheme:
Dictionary<string, Dictionary<string, List<Task>>>
I need to look for the productId and return that value (Task object).
It's usually not the case that a product key appears in several orders. So it's unique.
Here is a json example:
{
"O1": {
"P1": [
{
"Field": "V1"
},
{
"Field": "V7"
}
],
"P2": [
{
"Field": "V2"
},
{
"Field": "V8"
}
]
},
"O2": {
"P1": [
{
"Field": "V3"
},
{
"Field": "V5"
}
],
"P2": [
{
"Field": "V4"
},
{
"Field": "V6"
}
]
}
}
If I look for productId "P5" I want to get...
[{"Field":"V5"}]
The only way I found that runs is...
return base.Values.Single(x => x.ContainsKey(productId))[productId];
//base is the nested Dictionary
But I don't like what I'm doing here. Because I detach the correct dictionary from the collection (values) where the key exists and finally I fetch only the value via key filtering (collection[key]).
That are basically two steps, but I suspect there is an easier way with just one step. - I just can't find this one.
Maybe you can help me out. :)
For performance tuning, use HashSet instead of List.
I also can recommend ISBN: 0321637003 (LINQ to Objects), maybe there is an updated version. But anyway its still very good content.
If you have millions of entries in your dict, you can try PLINQ.
return base.Values.Single(x => x.ContainsKey(productId))[productId];
The Sample json throws an exception, because your productId (P1, P2) is not unique and P5 does not exist.
If I look for productId "P5" I want to get...
[{"Field":"V5"}]
There Is no P5
I am not sure its helping but, here is my sample code. I have put your json into a file and deserialize it.
var lFile = new FileInfo(#"C:\_test\data.json");
using var lReader = lFile.OpenText();
var lJsonStr = lReader.ReadToEnd();
var lDicDic = JsonConvert.DeserializeObject<Dictionary<string, Dictionary<string, List<ProductId>>>>(lJsonStr);
//var lTest1 = lDicDic.Values.Single(x => x.ContainsKey("P1"))["P1"]; //Not working, P1 is not unique!
var lTest2 = lDicDic["O2"]["P1"];
//contains => "Field": "V3" + "Field": "V5"
var lTest3 = lDicDic
.SelectMany(p => p.Value.Values.SelectMany(qItem => qItem))
.FirstOrDefault(qProducts => qProducts.Field == "V5");
//contains => "Field": "V5"
var lTest4 = lDicDic["O2"]["P1"].Last();
//contains => "Field": "V5"
EDIT:
After your Fiddler code I was able to test it.
public List<MyTask> GetByProductId(string productId)
{
var lProductDict = Tasks.Values.SingleOrDefault(x => x.ContainsKey(productId));
return lProductDict?.GetValueOrDefault(productId);
}
My stomach tells my, your current approach is hard to make any better ;)
If you have a lot of calls on this nasted dict, and its changing not very often it could make sense to refactor the dict from TopDown to BottomUp at the beginning for the processing.
Anyway, If my comments are helpful plz upvote my answere :)
If I were you I would make some testcases with real data.
Dont forget: If you are in DEBUG mode, to enable "Optimize Code" at the Project. In my testcase, I had an AVG of 47ms without and 42ms with optimized code:
You can give PLINQ a try:
https://learn.microsoft.com/en-us/dotnet/standard/parallel-programming/introduction-to-plinq
Here us my test scenario:
var lStopWatch = new Stopwatch();
lStopWatch.Restart();
var lJobs = new Jobs("F1");
const int TestOrderCount = 1000000;
const int TestAvgCount = 1000;
for (var lIndex = 1; lIndex < TestOrderCount; lIndex++)
{
var lNoStr = lIndex.ToString("D6");
lJobs.Add($"O{lNoStr}", $"P{lNoStr}", $"V{lNoStr}");
}
var GetTimes = new List<long>();
var lRandom = new Random();
var lTestCases = Enumerable
.Range(1, TestAvgCount - 1)
.Select(r => $"P{lRandom.Next(1, TestOrderCount - 1):D6}")
.ToList();
var lSetupTimeMs = lStopWatch.ElapsedMilliseconds;
Debug.WriteLine($"SetupTimeMs: {lSetupTimeMs}");
foreach (var lTestCase in lTestCases)
{
lStopWatch.Restart();
var lTest = lJobs.GetByProductId(lTestCase);
GetTimes.Add(lStopWatch.ElapsedMilliseconds);
}
var lAvg = GetTimes.Sum() / TestAvgCount; //AVG Ms per get
Debug.WriteLine($"AVG: {lAvg}");

Fetch Unique Column Values from a JArray

I am trying to get only Unique IDs from a JSON response. I tried to parse and select only token Ids but it is failing. What is the best and fastest way to get only the Ids?
Code:
JArray jArray = new JArray();
var response = new HttpResponseMessage(System.Net.HttpStatusCode.OK);
response.Content = new StringContent(jArray.ToString(Formatting.Indented));
var resp = await response.Content.ReadAsStringAsync();
Here is my sample JSON Array:
[
{
"Timestamp": "2020-11-24T08:25:46.6531855Z",
"ID": "8c316aca-b930-421f-851c-17d618b61fa1",
"Message": "New User Registered"
},
{
"Timestamp": "2020-11-24T08:25:46.6531855Z",
"ID": "8c316aca-b930-421f-851c-17d618b61fa1",
"Message": "User details updated"
},
{
"Timestamp": "2020-11-24T08:25:46.6531855Z",
"ID": "a23shaga-2wd3-fky6-851c-43524fbfgsa",
"Message": "New User Registered"
}
]
Desired Output:
8c316aca-b930-421f-851c-17d618b61fa1
a23shaga-2wd3-fky6-851c-43524fbfgsa
This will give you the distinct ID values from your JArray, filtering out possible nulls:
jArray = JArray.Parse(resp);
var ids = jArray.Children<JObject>()
.Select(jo => (string)jo["ID"]) // NOTE: this is case sensitive
.Where(s => s != null)
.Distinct()
.ToList();
Demo here: https://dotnetfiddle.net/gaQ1n0
Best way is to create the object with the properties timestamp, id and message- then deserialize the json to this object.
But for now you can try below and should work out well for you.
JArray jArray = new JArray();
jArray = JArray.Parse(#"[
{
""Timestamp"": ""2020-11-24T08:25:46.6531855Z"",
""ID"": ""8c316aca-b930-421f-851c-17d618b61fa1"",
""Message"": ""New User Registered""
},
{
""Timestamp"": ""2020-11-24T08:25:46.6531855Z"",
""ID"": ""8c316aca-b930-421f-851c-17d618b61fa1"",
""Message"": ""User details updated""
},
{
""Timestamp"": ""2020-11-24T08:25:46.6531855Z"",
""ID"":""a23shaga-2wd3-fky6-851c-43524fbfgsa"",
""Message"": ""New User Registered""
}]");
var newList = jArray.Where(y=> !string.IsNullOrEmpty(y["ID"].ToString()))
.GroupBy(x => x["ID"])
.Select(x => x.First()).ToList();
foreach (var item in newList)
{
Console.WriteLine(item["ID"]);
}
Here newList contains only unique data w.r.t 'ID'
Just replace the entire string in my example with the async resp you are getting.
NOTE : You should either make the source return unique or create a new object and deserialize it instead of using JObject, this will help you when you have bigger lists
So below is the outcome i think you are looking for

How to return a List of all values in a field

I have a document like:
{
"Id":"1",
"Name":"product1",
"Categories":["Cat1",
"Cat2",
"Cat3"]
},
{
"Id":"2",
"Name":"product2",
"Categories":["Cat3",
"Cat2",
"Cat6"]
}
Now I want return a distinct List of all categories.
I tried CollapseParameter, but it doesn't work.
var foo = _solr.Query(new SolrQuery("*"), new QueryOptions
{
Collapse = new CollapseParameters("Categories")
});
var bar= results.CollapseExpand; //NULL
How can I get a list of all categories without iterating through all the documents? Do I have to create new Documents for the categories? Or should I work with Faceting?
It looks like the JSON is not valid. So I created a solution changing the Json:
{
"items": [{
"Id": "1",
"Name": "product1",
"Categories": ["Cat1", "Cat2", "Cat3"]
}, {
"Id": "2",
"Name": "product2",
"Categories": ["Cat3", "Cat2", "Cat6"]
}]
}
And then, I used foreach and LINQ structure:
var x = categories.SelectMany(item => item.Categories).Distinct();
If you are using solr and you want all distinct value for specific field then must start reading solr faceting doc, its amazing.
Only add below query in your query and you we get full list of all distinct categories.
&facet=true&facet.field=Categories
That's it, It will gives result set as you expected.
QueryOptions options = new QueryOptions
{
StartOrCursor = new StartOrCursor.Start(0),
Rows = 1,
Facet = new FacetParameters { Queries = new[] { new SolrFacetFieldQuery("Categories") } }
};
var result = _solrConnection.Query(new SolrQuery("*"), options);
if (result.FacetFields.ContainsKey("Categories"))
{
return result.FacetFields["Categories"]
.Where(li => li.Value > 0)
.Select(y=> y.Key)
.ToList();
}
That's my current solution. It works, but I hoped there would be a better way. I'm searching for a result just to get all the categories.

Categories