JSON.NET - getting particular properties inside objects from Jarray - c#

There is a series of JSON objects called "issues", which each have one or more "issue links", which have the following format:
// an issue link
{
"id": "000000",
"self": "some link",
"type": {
"id": "0000",
"name": "some name",
"inward": "is met by",
"outward": "meets",
"self": "some link"
},
"outwardIssue": {
"id": "000000",
"key": "the required key",
"self": "some link",
"fields": {
// the remainder is not applicable
}
}
}
}
These "issue links" have been extracted as follows. Create a JArray for the JSON for the "issue" itself, and extract the child JObjects:
public void Deserialize(dynamic jsonObject)
{
// get the issue links
if (jsonObject["fields"]["issuelinks"]!=null)
{
JArray issueLinksArray = jsonObject["fields"]["issuelinks"];
var issueLinkObjects = issueLinksArray.Children();
foreach (var issueLink in issueLinkObjects)
{
// now need the "key" in the "outwardIssue" for this object, if the value of "inward" is "is met by".
}
}
}
How to go about extracting the value of the second property "key" of "outwardIssue"?

Not sure whether I fully understand but following excerpt gets u the value (or null if condition not met) like this.
var key = issueLink["type"]["inward"].ToString()=="is met by" ? issueLink["outwardIssue"]["key"]: null;
Hint: Try to avoid dynamic.
Nowadays loops can in certain conditions considered old-school. Think LINQ: The problem can be divided into smaller probs and distributed across multiple lines (think-steps).
The additional variables might improve readability. As the project grows, for loops are prone to span more and more lines. So if you just need requested values following might be of interest:
var inwardLinks = issueLinkObject.Where(i=>i["type"]["inward"].ToString()=="is met by");
var keys = inwardLinks.Select(iwl=>iwl["outwardIssue"]["key"]);

Related

Parsing API Response To See if All Sku Tags are Present

I am fairly new to c# and am trying to parse an api response. My goal is to check each sku present to see if it contains all three of the following tags: Dot, Low, and Default. The only thing is the api is set up a bit odd, so even if the "Rskuname" is the same, its listed under a different skuID. I need to make sure each Rskuname contains all of the 3 types, here is an example of the api below (parts of it have been omitted since its a huge amount of data, just showing the pieces important to this question)
"Skus": [
{
"SkuId": "DH786HY",
"Name": "Stand_D3_v19 Dot",
"Attributes": {
"RSkuName": "Stand_D3_v19",
"Category": "General",
"DiskSize": "0 Gb",
"Low": "False",
},
"Tags": [
"Dot"
{
"SkuId": "DU70PLL1",
"Name": "Stand_D3_v19",
"Attributes": {
"Attributes": {
"RSkuName": "Stand_D3_v19",
"Category": "General",
"DiskSize": "0 Gb",
"Low": "False",
},
"Tags": [
"Default"
]
{
"SkuId": "DPOK65R4",
"Name": "Stand_D3_v19 Low",
"Attributes": {
"Attributes": {
"RSkuName": "Stand_D3_v19",
"Category": "General",
"DiskSize": "0 Gb",
"Low": "True",
},
"Tags": [
"Low"
],
{
"SkuId": "DPOK65R4",
"Name": "Stand_D6_v22 Low",
"Attributes": {
"Attributes": {
"RSkuName": "Stand_D6_v22",
"Category": "General",
"DiskSize": "0 Gb",
"Low": "True",
},
"Tags": [
"Low"
],
Originally I tried to iterate through each sku, however since the skuids are different even though the name is the same that doesnt work. I was thinking of possibly using a string, hashset dictionary so it would go skuName:Tags but I'm not sure that will work either. Any ideas would be much appreciated. Apologies if this question isn't phrased well, once again I'm a beginner. I have included what I tried originally below:
foreach (Sku sku in skus)
{
string SkuName = sku.Attributes[RSkuName];
var count = 0;
if (sku.Tags.Equals(Default))
{
count++;
}
if (sku.Tags.Equals(Low))
{
count++;
}
if (sku.Tags.Equals(Dot))
{
count++;
}
}
if (count < 3)
{
traceSource.TraceInformation($"There are not 3 tags present for" {SkuName} );
}
Seems simple:
group by RSkuName
group by Tag element value
make sure there is a group for each of the required tag values.
Yes you could use a hashset in this scenario to formulate the groups. If we had to do it from first principals that that's not a bad idea.
However we can use the LINQ fluent GroupBy function (which uses a hashset internally) to iterate over the groups.
There is only one complicating factor to this that even your first attempt does not take into account, Tags is an array of strings, so to properly group the values across multiple arrays so we can use GroupBy we can use the SelectMany function to merge the arrays from multiple SKUs into a single set, then GroupBy becomes viable again.
Finally, if the only possible values for Tag elements are Dot, Low, and Default. Then we only need to count the groups and make sure there are 3 to make the SKU valid.
bool notValid = skus.GroupBy(x => x.Attributes[RSkuName])
.Any(sku => sku.SelectMany(x => x.Tags)
.GroupBy(x => x)
.Count() < 3)
I call this a fail fast approach, instead of making sure ALL items satisfy the criteria we only try to detect the first time that the criteria is not met and stop processing the list
If other tags might be provided then we can still use similar syntax by filtering the tags first:
string[] requiredTags = new string [] { "Dot", "Low", "Default" };
bool notValid = skus.GroupBy(x => x.Attributes[RSkuName])
.Any(sku => sku.SelectMany(x => x.Tags)
.Where(x => requiredTags.Contains(x))
.GroupBy(x => x)
.Count() < 3)
If you need to list out all the skus that have failed, and perhaps why they were not valid, then we can do that with similar syntax. Instead of using LINQ though, lets look at how you might do this with your current iterator approach...
Start by creating a class to hold the tags that we have seen for each sku:
public class SkuTracker
{
public string Sku { get; set; }
public List<string> Tags { get;set; } = new List<string>();
public override string ToString() => $"{Sku} - ({Tags.Count()}) {String.Join(",", Tags)}";
}
Then we maintain a dictionary of these SkuTracker objects and record the tags as we see them:
var trackedSkus = new Dictionary<string, SkuTracker>();
...
foreach (Sku sku in skus)
{
string skuName = sku.Attributes[RSkuName];
if (!trackedSkus.ContainsKey(skuName))
trackedSkus.Add(skuName, new SkuTracker { Sku = skuName };
trackedSkus.Tags.AddRange(sku.Tags);
}
...
var missingSkus = trackedSkus.Values.Where(x => x.Tags.Count() < 3)
.ToList();
foreach(var sku in missingSkus)
{
traceSource.TraceInformation($"There are not 3 tags present for {sku.Sku}" );
}
Looking at the JSON fragment you have provided, I suspect that you could only verify the validation after the entire list was processed, so we cannot trace out the failure messages in the first iteration, this current code will still produce the same output as if we had though.
NOTE: In the SkuTracker we have defined the ToString() override, this is so that you could easily view the content of the tracked SKUs in the debugger using the native viewers, especially if you try to inspect the content of missingSkus.

A possible object cycle was detected when serializing entity-model to JSON

I have a webapi project that written using C# and built on the top of ASP.NET Core/5 framework.
The API return JSON data.
Here is an example of one endpoint
[HttpGet]
public async Task<ActionResult<TModel[]>> QueryAsync(
[FromQuery(Name = "filter")] string filter = null,
[FromQuery(Name = "expand")] string expand = null)
{
IQueryable<TModel> query = GetQuery(filter, expand);
var models = await query.ToArrayAsync();
return Ok(models);
}
In the above request, the expand will tell the server which navigation properties to expand. When the user request to expand a property, I get the following error
A possible object cycle was detected. This can either be due to a cycle or if the object depth is larger than the maximum allowed depth of 32. Consider using ReferenceHandler.Preserve on JsonSerializerOptions to support cycles
Following the instruction from the error, I added the following to the Startup class
services.AddControllers().AddJsonOptions(options =>
{
options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter());
// added this
options.JsonSerializerOptions.ReferenceHandler = ReferenceHandler.Preserve;
});
The above change did fix the problem! However, it introduced another problem. The new problem is that it changed the structure of the output.
The output went from something like this
{
// properties
"relations": [
{
// relation 1
"parent": null
},
{
// relation 2
"parent": null
}
]
}
to something like this
{
"$id": "1",
// properties
"relations": {
"$id": "2",
"$values": [
{
// relation 1 properties
"$id": "3",
"parent": {
"$ref": 1
}
},
{
// relation 2 properties
"$id": "3",
"parent": {
"$ref": 1
}
}
]
}
}
Is there a way to not change the structure of the output and instead ignore the circular reference?
There is a similar question here, where one of the answers mentions the same issue you're having: .Net Core 3.0 possible object cycle was detected which is not supported
Some of the things mentioned there:
If you're on .NET 6 you can use
JsonSerializerOptions options = new()
{
ReferenceHandler = ReferenceHandler.IgnoreCycles,
WriteIndented = true
};
alternatively, depending on what you need to achieve, you might want to just ignore looping properties altogether.
The proper solution though would be to find out where exactly your references are looping and exclude the property(s) in question from serialization, or supply them in a format that doesn't loop (such as an id or similar)

How to search in dynamodb with a complex value using .net sdk

I have just started with DynamoDB. I have background in MongoDB and relational databases and I am structuring my JSON in more like a graph structure than a flat structure. For example,
[
{
"id": "1",
"title": "Castle on the hill",
"lyrics": "when i was six years old I broke my leg",
"artists": [
{
"name": "Ed Sheeran",
"sex": "male"
}
]
}
]
For example, If I like to search the item by 'Ed Sheeran'. The closest I have got is this and this is not even matching any value.
var request = new ScanRequest
{
TableName = "Table",
ProjectionExpression = "Id, Title, Artists",
ExpressionAttributeValues = new Dictionary<string,AttributeValue>
{
{ ":artist", new AttributeValue { M = new Dictionary<string, AttributeValue>
{
{ "Name", new AttributeValue { S = "Ed Sheeran" }}
}
}
}
},
ExpressionAttributeNames = new Dictionary<string, string>
{
{ "#artist", "Artists" },
},
FilterExpression = "#artist = :artist",
};
var result = await client.ScanAsync(request);
Most of the example and tuturials I have watched so far, they have treated dynamodb as a table in a normal relational database with very flat design. Am I doing it wrong to structure the JSON as above? Should Artists be in a separate table?
And If it can be done, how do i search by some value in a complex type like in the above example?
First of all, you should not be using the scan operation in dynamodb. I would strongly recommend to use query. Have a look at this stack overflow question first.
If you want to search on any attribute, you can either mark them as the primary key (either hash_key or hash_key + sort_key) or create an index on the field you want to query on.
Depending on the use case of id attribute in your schema, if you are never querying on id attribute, I would recommend the structure something like this :
[
{
"artist_name" : "Ed Sheeran" // Hash Key
"id": "1", // Sort Key (Assuming id is unique and combination of HK+SK is unique)
"title": "Castle on the hill",
"lyrics": "when i was six years old I broke my leg",
"artists": [
{
"name": "Ed Sheeran",
"sex": "male"
}
]
}
]
Alternatively, if you also need to query on id and it has to be the hash key, you can an index on the artist_name attribute and then query it.
[
{
"artist_name" : "Ed Sheeran" // GSI Hash key
"id": "1", // Table Hash key
"title": "Castle on the hill",
"lyrics": "when i was six years old I broke my leg",
"artists": [
{
"name": "Ed Sheeran",
"sex": "male"
}
]
}
]
In either case, it is not possible to query inside a nested object without using scan operation and then iterating it in code, something which you have already tried.

How to pull a specific value from dynamic JSON dictionary in C#

I'm trying to pull one specific piece of data from the json received from a geocoding request.
This is not a duplicate of the myriads of similar-sounding questions, because the json is dynamic and has extremely irregular arrays that change slightly between responses, so I can't create a model to parse to, neither can I navigate through it using key names.
This is the json of one response:
{
"Response":{
"MetaInfo":{
"Timestamp":"2019-07-28T13:23:04.898+0000"
},
"View":[
{
"_type":"SearchResultsViewType",
"ViewId":0,
"Result":[
{
"Relevance":1.0,
"MatchLevel":"houseNumber",
"MatchQuality":{
"City":1.0,
"Street":[
0.9
],
"HouseNumber":1.0
},
"MatchType":"pointAddress",
"Location":{
"LocationId":"NT_Opil2LPZVRLZjlWNLJQuWB_0ITN",
"LocationType":"point",
"DisplayPosition":{
"Latitude":41.88432,
"Longitude":-87.63877
},
"NavigationPosition":[
{
"Latitude":41.88449,
"Longitude":-87.63877
}
],
"MapView":{
"TopLeft":{
"Latitude":41.8854442,
"Longitude":-87.64028
},
"BottomRight":{
"Latitude":41.8831958,
"Longitude":-87.63726
}
},
"Address":{
"Label":"425 W Randolph St, Chicago, IL 60606, United States",
"Country":"USA",
"State":"IL",
"County":"Cook",
"City":"Chicago",
"District":"West Loop",
"Street":"W Randolph St",
"HouseNumber":"425",
"PostalCode":"60606",
"AdditionalData":[
{
"value":"United States",
"key":"CountryName"
},
{
"value":"Illinois",
"key":"StateName"
},
{
"value":"Cook",
"key":"CountyName"
},
{
"value":"N",
"key":"PostalCodeType"
}
]
}
}
}
]
}
]
}
}
I'm trying to get just the two coordinates inside NavigationPosition.
Before I start the tedious work of creating a method to manually pull out the data I need from the unparsed string, does anyone have a solution? Is there a way to iterate anonymously through the json arrays using a foreach or if statement?
If the response is dynamic then one option is to use dynamic deserialization
var dynamicObject = JsonConvert.DeserializeObject<dynamic>(jsonAsString);
You can iterate this object and write defensive code by checking null.
I am assuming that "NavigationPosition" will always lie under "location" and in turn under a list of "Result" under list of "View"
something like this.. (symbolic code)
if(dynamicObject.View != null && dynamicObject.View[0] != null && dynamicObject.View[0].Result[0] != null && dynamicObject.View[0].Result[0].Location != null )
{
var navigationPosition = dynamicObject.View[0].Result[0].Location.NavigationPosition;
// do something with it
}
It seems really cumbersome but if you have no control over json response, I think you should be able to get it right after some iterations :)

How to get sys_id from the result(incident_response) from below c# code

I have the following JSON(modified) which is read from a HttpWebRequest response stream, and I want to extract the value of "sys_id":
{
"result": {
"number": "INC0012618",
"opened_by": {
"link": "//XXX.service-now.com/api/now/v1/table/sys_user/38723e5ddb42f200deb8f6fcbf96196d",
"value": "38723e5ddb42f200deb8f6fcbf96196d"
},
"sys_created_on": "2017-07-20 14:41:52",
"sys_domain": {
"link": "://XXX.service-now.com/api/now/v1/table/sys_user_group/global",
"value": "global"
},
"u_incident_summary": "",
"business_service": {
"link": "://xxx.service-now.com/api/now/v1/table/cmdb_ci_service/IT services",
"value": "IT services"
},
"work_end": "",
"caller_id": {
"link": "://xxxx.service-now.com/api/now/v1/table/sys_user/71f5455d4f735e000d7f0ed11310c719",
"value": "71f5455d4f735e000d7f0ed11310c719"
},
"close_code": "",
"assignment_group": {
"link": "://xxx.service-now.com/api/now/v1/table/sys_user_group/9e158987dba27a007ea0f4e9bf961983",
"value": "9e158987dba27a007ea0f4e9bf961983"
},
"sys_id": "7fb4e50edb8c0b007ea0f4e9bf9619ba",
"u_outage_start_time": ""
}
}
This is what I have tried so far, where incident_responce is a string containing the JSON above:
var jObject = JObject.Parse(incident_responce);
var value = (string)jObject["sys_id"];
Console.WriteLine(value);
But, it didn't work, I think because there is "result" at the start. How can I retrieve this value?
As you suspected, your initial attempt fails because "sys_id" is nested inside the "result" object:
{ "result": { ... "sys_id":"7fb4e50edb8c0b007ea0f4e9bf9619ba" } }
It's easier to see this if you indent and format your JSON, for instance by uploading it to https://jsonformatter.curiousconcept.com/.
Such a nested value can be queried directly by using JToken.SelectToken():
var root = JToken.Parse(incident_responce);
var value = (string)root.SelectToken("result.sys_id");
SelectToken() supports querying for values deep within the JSON container hierarchy using JSONPath syntax. If the "result.sys_id" token is not found, null is returned.
Alternatively, you could use Json.NET's support for querying JSON hierarchies using dynamic functionality:
dynamic root = JToken.Parse(incident_responce);
var value = (string)root.result.sys_id;
However, if the "result" token is not found, a RuntimeBinderException will be thrown instead of a null value returned.
Working .Net fiddle.

Categories