I've done a lot of searching at it appears there is no article for this specific case.
I have some JSON (Note that it is a JArray, and not a JObject)
[
{
"Name": "Name 1",
"Icon": "Icon 1",
"Samples": [
{
"Name": "Sample 1",
},
{
"Name": "Sample 2",
},
{
"Name": "Sample 3",
}
]
},
{
"Name": "Name 2",
"Icon": "Icon 2",
"Samples": [
{
"Name": "Sample 1",
},
{
"Name": "Sample 2",
},
{
"Name": "Sample 3",
}
]
},
{
"Name": "Name 3",
"Icon": "Icon 3",
"Samples": [
{
"Name": "Sample 1",
},
{
"Name": "Sample 2",
},
{
"Name": "Sample 3",
}
]
},
]
So this has 3 levels. The root, which contains the "SampleCategories". The "SampleCategories" which contains some items and a "Samples" list, and the "Samples" list which contains many objects.
So, what I'm essentially trying to do here is to add an object into the Samples list, inside a specific SampleCategory. So for example:
I want to add "Sample 4" to the Samples array in "Name 1" so it looks like this:
{
"Name": "Name 1",
"Icon": "Icon 1",
"Samples": [
{
"Name": "Sample 1",
},
{
"Name": "Sample 2",
},
{
"Name": "Sample 3",
},
{
"Name": "Sample 4",
}
]
},
I thought this would be fairly easy when I started out but it turns out there are lots of roadblocks that I can't seem to get my head around.
So I have two models for each level, "SampleCategory" for the top level that has the Name, Icon and Samples, which goes off to another model called "Samples[]"
So I'm afraid I'm at an embarrassing point in this process... Right at the start. I've de-serialised the json, and done some bits around finding my chosen SampleCategory, and created an object ready to add in, and that's as far as I've gotten...
var categories = JsonConvert.DeserializeObject<List<SampleCategory>>(json);
var applications = categories.Find(c => c.Name.Equals("Name 1"));
var sample = new Sample
{
Name = "Sample 4",
};
I don't appear to be able to find any way of adding a new 'sample' into the Samples list of a chosen SampleCategory. I even tried adding to the object path directly, the path of which I got using this:
JObject jo = jsonArray.Children<JObject>().FirstOrDefault(o => o["Name"] != null && o["Name"].ToString() == "Name 1");
But there doesn't appear to be a way to add to an array as I guess it's a static length?
What is best practice modifying JSON like this?
Documentation (https://www.newtonsoft.com/json/help/html/ModifyJson.htm) suggests there a few ways to do it when playing around with JObjects, but it doesn't suggest a way to do it with nested objects inside of JArrays.
Can anyone help or suggest some methods to do this? I'm sure it's probably simple, but I'm going through a loop trying to figure it out.
I guess your model looks something like this:
public class Sample
{
public string Name { get; set; }
}
public class SampleCategory
{
public string Name { get; set; }
public string Icon { get; set; }
public List<Sample> Samples { get; set; }
}
If you are not sure how a model must look like to match a given json string you can make use of a tool like json2csharp which will generate the model for you.
To be able to add a new sample to a specific category you can find the specific category using LINQ after deserializing it:
var categories = JsonConvert.DeserializeObject<List<SampleCategory>>(json);
var category = categories.FirstOrDefault(c => c.Name == "Name 1");
Now (if a category with the name exists) just add the new sample to the list:
if(category != null)
{
category.Samples.Add(new Sample{Name = "Sample 4"});
}
To serialize it back to json:
var json = JsonConvert.SerializeObject(categories);
Edit
If you cannot switch your model type for Samples to List<T> and you don't want to convert the array temporarily to List<T>, you can do something like this:
if(category != null)
{
category.Samples = category.Samples.Concat(new Sample[]{new Sample{Name = "Sample 4"}}).ToArray();
}
If you are not bothered by an additional List<T> cast another approach would be:
if(category != null)
{
var tmpList = category.Samples.ToList();
tmpList.Add(new Sample{Name = "Sample 4"});
category.Samples = tmpList.ToArray();
}
I'm not sure but it could be as simple as :
applications.Samples.ToList().Add(sample);
I have no privilege to comment it :(
Related
I'm working with an external API to get some product information, the end points return some data in a static structure and others in dynamic depending on the product I'm inquiring.
For example if I'm requesting data for a soap I get the following JSON:
{ "id": 4623,
"brand": "Fa",
"category": "Cleansing/Washing/Soap – Body",
"photos": {
"name": "Photos",
"value": [ "https//test.com/1jpg"
]
},
"productname": {
"name": "Product Name",
"value": "Fa Shower Cream Yoghurt Vanilla Honey"
},
"warningstatement": {
"name": "Warning Statement",
"value": "Avoid contact with eyes."
},
"consumerusageinstructions": {
"name": "Consumer Usage Instructions",
"value": "Apply directly on skin."
}
and if I'm inquiring about a cheese I get the following JSON:
{
"id": 10838,
"brand": "Domty",
"category": "Cheese",
"photos": {
"name": "Photos",
"value": [ "https://test.com/2.jpg"
]
},
"productname": {
"name": "Product Name",
"value": "Domty White Low Salt Cheese"
},
"description": {
"name": "1312",
"value": "Highest premium quality"
},
"netcontent": {
"name": "Net Content",
"value": "900 gm"
}
and it goes on for every product they offer. I've no problem deserializing the static data like photos array, product name, brand, and id since they are applicable to every product, but the other dynamic properties are the ones I'm concerned about. Is there a way to deserialize to a class like this:
public class Info {
property string key { get; set;} // to hold description, consumerusageinstructions or what every key
property string name { get; set;}
property string value { get; set;}
}
and then add a collection of the class info to my product model?
One way is just to parse the Json and look at the actual entities: this example uses Json.Net:
var parsed = JObject.Parse(json);
var properties = parsed.Children().Cast<JProperty>();
foreach (var property in properties) {
// an alternative here would be to just have a list of names to ignore
if (!(property.Value is JObject jObject)) {
// skip the simple property/value pairs
continue;
}
if (property.Name == "productname") {
// skip product name
continue;
}
if (property.Value["value"] is JArray) {
// skip photos
continue;
}
// Add to ProductModel instance
Console.WriteLine($"{property.Name} => {property.Value["name"]} = {property.Value["value"]}");
}
Outputs:
warningstatement => Warning Statement = Avoid contact with eyes.
consumerusageinstructions => Consumer Usage Instructions = Apply directly on skin.
description => 1312 = Highest premium quality
netcontent => Net Content = 900 gm
I've ObservableCollection<dynamic> called myJSON in C# having 4 items as follows:
[
{
"name": "A",
"location": "NY"
},
{
"name": "B",
"location": "NJ"
},
{
"name": "A",
"location": "NY"
},
{
"name": "D",
"location": "MA"
}
]
I need to be able to apply a filter query like say where name="A" and location="NY" and then get back 2 records from above.
I tried code like below but I was only able to parse one record at a time from the above collection. And also the 2nd line seems to error out with a message:
"Cannot access child value on Newtonsoft.Json.Linq.JValue."
JObject json = JObject.Parse(myJSON[0].ToString());
var match = json.Values<JProperty>().Where(m => m.Value["name"].ToString() == "A" && m.Value["location"].ToString() == "NY").FirstOrDefault();
Thanks.
Here:
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using Newtonsoft.Json;
namespace NameLoc
{
class Program
{
class NameLoc
{
public string Name;
public string Location;
}
static void Main(string[] args)
{
var lst = JsonConvert.DeserializeObject<ObservableCollection<NameLoc>>(File.ReadAllText("NameLoc.json"));
var selLst = from sel in lst where sel.Name == "A" && sel.Location == "NY" select sel;
foreach (var it in selLst)
{
Console.WriteLine($"{it.Name}, {it.Location}");
}
Console.WriteLine("Hello World!");
}
}
}
And then be sure to add a file "NameLoc.Json" Set build Action Copy To output I found errors in your json corrected below. Add Newtonsoft.json to pkgs
[
{
"name": "A",
"location": "NY"
},
{
"name": "B",
"location": "NJ"
},
{
"name": "A",
"location": "NY"
},
{
"name": "D",
"location": "MA"
}
]
First, let's fix json format. If you are talking about collection or array then your json format should be like this:
[
{
"name": "A",
"location": "NY"
},
{
"name": "B",
"location": "NJ"
},
{
"name": "A",
"location": "NY"
},
{
"name": "D",
"location": "MA"
}
]
And you should use JArray instead of JObject to parse it so that you can apply filter.
JArray jsonArray = JArray.Parse(jsonString);
var match = jsonArray.Where(i => i["name"].ToString() == "A" && i["location"].ToString() == "NY").ToList();
You do know that your json array is incorrect, don't you? An array is within square brackets [ ... ]. I assume that is a typing error.
My advice would be to separate your concerns: split your problem into separate parts:
I have a string in JSON format containing information about Persons (?), and I need to convert this into a sequence of Persons
I have a sequence of Persons, I need only those Persons with a certain Name and Location.
If you do this, your code will be easier to understand, more reusable, easier to test and easier to change. Who does not want that?
Convert string to a sequence of Persons
For this I write an extension function. This way it looks more like LINQ. See extension methods demystified
public class Person
{
public string Name {get; set;}
public string Location {get; set;}
... // other properties
}
// Converts a JToken to a Person:
public static Person ToPerson(this JToken token)
{
// TODO: decide what to do if token == null. Return null?
Person person = token.ToObject<Person>();
return person;
}
// Converts JSON string to sequence of Persons
public static IEnumerable<Person> ToPersons(this string jsonText)
{
// TODO: exception if jsonText null, or incorrect
IEnumerable<JToken> jsonArray = JArray.Parse(jsonString);
foreach (JToken token in jsonArray)
{
Person person = token.ToPerson();
yield return person;
}
}
Usage looks like LINQ ToList():
string jsonText = ...
IEnumerable<Person> persons = jsonText.ToPersons();
Keep only Persons with certain name and location
After you've converted the json to a sequence of Persons, the rest is standard LINQ
const int name = "John Doe";
const int location = "New York City";
string jsonText = ...
var result = jsonText.ToPersons()
.Where(person => person.Name == name && person.Location == location);
Advantages of this separation of concerns
This Separation has several advantages:
You can reuse ToPersons() for other LINQ statements: "give me all elderly Persons", "give me the cities where my Persons live".
It is easier to test: just create an array of test persons as a source for your LINQ statements
It is easier to understand: people who read your LINQ statements don't have to bother anymore about the conversion: they know the conversion is used already in a lot of other places, and the test software succeeds, hence you can trust the conversion.
It is easier to change: if you want to add a PostCode to your Persons, go ahead, your LINQ statements won't change, only ToPerson()
I am new to linq and trying to figure this list merge out. I am trying to merge 2 lists of parent/child data as follows:
{
"ParentsList1":
[
{
"Name": "Parent 1",
"Children": [
{
"Name": "Child 1",
},
{
"Name": "Child 2",
}
],
},
{
"Name": "Parent 2",
"Children": [
{
"Name": "Child 1",
}
],
}
]
}
{
"ParentsList2":
[
{
"Name": "Parent 1",
"Children": [
{
"Name": "Child 1",
},
{
"Name": "Child 2",
},
{
"Name": "Child 3",
}
],
},
{
"Name": "Parent 2",
"Children": [
{
"Name": "Child 2",
}
],
}
]
}
---------------Merged Output----------------
{
"MergedParentsList":
[
{
"Name": "Parent 1",
"Children": [
{
"Name": "Child 1",
},
{
"Name": "Child 2",
},
{
"Name": "Child 3",
}
],
},
{
"Name": "Parent 2",
"Children": [
{
"Name": "Child 2",
}
],
}
]
}
I would like the merge to remove/replace child entities. So far I have been stuck working with a union
List<ParentListModel> mergedParentLists = ParentsList1
.Union(ParentsList2)
.GroupBy(grp => new {grp.Name})
.Select(sel => sel.FirstOrDefault())
.ToList();
This seems to get the Parent list ok but cannot seem to sync up the child data...
It sounds like you're considering a person to be identical if they have the same Name, so if you join two lists then any parents who share the same name should be "merged" into one person, and in this process, their children should be merged in the same way (if they both have a child named "Child 1", then after the merge there will be only one "Child 1", but otherwise all children from both instances of the parent exist in the merged instance of the parent).
If this is the case, then I think what you want to do is Select new Person() from your group, where you take the group Key as the person's name, and add the children by using SelectMany on all the children in the group and then taking only the unique ones by using DistinctyBy(child.Name).
For example:
List<ParentListModel> mergedParents = parentList1
.Union(parentList2)
.GroupBy(parent => parent.Name)
.Select(group =>
new ParentListModel
{
Name = group.Key,
Children = group
.SelectMany(parent => parent.Children)
.DistinctBy(child => child.Name)
.ToList()
})
.ToList();
Also, there is some ambiguity in your question, where in the comments you mentioned that during a merge, parentList2 should "win". If by this you mean you want to only add parents from parentList1 that don't exist in parentList2, then you can simply do an AddRange where you add parents whose name doesn't exist in parentList2:
parentList2.AddRange(parentList1.Where(pl1Parent =>
parentList2.All(pl2Parent => pl2Parent.Name != pl1Parent.Name)));
The ambiguity exists because your example holds true for both of these scenarios. It would be better if you included an example that excluded one of these (for example, if Parent 1 in parentList1 had a child named Child 9...would that child exist in the final merge or not?).
Following this example:
https://learn.microsoft.com/en-us/azure/cognitive-services/luis/luis-quickstart-cs-add-utterance
I am trying to send an utterance to my LUIS App.
It keeps on failing with this response message:
{
"error": {
"code": "BadArgument",
"message": "Failed to parse example labeling objects. Parameter name: exampleLabelObjects"
}
}
My input body is:
{
"text": "hi, what can I help you with?",
"intentName": "Help",
"entityLabels": []
}
And according too the link if you send an utterance without any entity labels the above is correct.
The entityLabels field is required. If you don't want to label any
entities, provide an empty list as shown in the following example:
[
{
"text": "go to Seattle",
"intentName": "BookFlight",
"entityLabels": [
{
"entityName": "Location::LocationTo",
"startCharIndex": 6,
"endCharIndex": 12
}
]
},
{
"text": "book a flight",
"intentName": "BookFlight",
"entityLabels": []
}
]
The C# to build the object is as follows:
public class LUISUtterItem
{
public string utterances;
public string text;
public string intentName;
public List<exampleLabelObjects> entityLabels;
}
public class exampleLabelObjects
{
public string entityName;
public int startCharIndex;
public int endCharIndex;
}
I call it using:
LUISUtterItem itm = new LUISUtterItem();
//itm.utterances = materialArray[1];
itm.text = materialArray[1];
itm.intentName = materialArray[2];
itm.entityLabels = new List<exampleLabelObjects>();
I have also tried not including an "entityLabels" object, as well as a string list that just gets initiated with the same result.
Any help will be appreciated.
So it seems like all you have to include in the body is "[]" around it and it worked:
[{
"text": "hi, what can I help you with?",
"intentName": "Help",
"entityLabels": []
}]
I faced the same issue. Solved by sending the body as:
{
"text": "hi",
"intentName": "Greetings"
}
Don't put entityLabel if it is not needed.
I've got a JSON stream coming back from a server, and I need to search for a specific value of the node "ID" using JSON.net to parse the data.
And I can almost make it work, but not quite because the results coming back are deeply nested in each other -- this is due to the fact that I'm getting a folder structure back. I've boiled the JSON down to a much simpler version. I'm getting this:
{
"data": {
"id": 0,
"name": "",
"childFolders": [{
"id": 19002,
"name": "Locker",
"childFolders": [{
"id": 19003,
"name": "Folder1",
"childFolders": [],
"childComponents": [{
"id": 19005,
"name": "route1",
"state": "STOPPED",
"type": "ROUTE"
}]
}, {
"id": 19004,
"name": "Folder2",
"childFolders": [],
"childComponents": [{
"id": 19008,
"name": "comm1",
"state": "STOPPED",
"type": "COMMUNICATION_POINT"
}, {
"id": 19006,
"name": "route2",
"state": "STOPPED",
"type": "ROUTE"
}, {
"id": 19007,
"name": "route3",
"state": "STOPPED",
"type": "ROUTE"
}]
}],
"childComponents": []
}],
"childComponents": []
},
"error": null
}
I can almost get there by going:
var objects = JObject.Parse(results);
var subobjects = objects["data"]["childFolders"][0]["childFolders"][1];
I can see in the debug view that it'll parse the object, but won't let me search within.
My ultimate goal is to be able to search for "route3" and get back 19007, since that's the ID for that route. I've found some results, but all of them assume you know how far nested the object is. The object I'm searching for could be 2 deep or 20 deep.
My ultimate goal is to be able to search for "route3" and get back 19007
You can use linq and Descendants method of JObject to do it:
var dirs = JObject.Parse(json)
.Descendants()
.Where(x=>x is JObject)
.Where(x=>x["id"]!=null && x["name"]!=null)
.Select(x =>new { ID= (int)x["id"], Name = (string)x["name"] })
.ToList();
var id = dirs.Find(x => x.Name == "route3").ID;
You can use the SelectToken or SelectTokens functions to provide a JPath to search for your desired node. Here is an example that would provide you the route based on name:
JObject.Parse(jsonData)["data"].SelectToken("$..childComponents[?(#.name=='route3')]")
You can find more documentation on JPath here
Simply write a recursive function:
private Thing FindThing(Thing thing, string name)
{
if (thing.name == name)
return thing;
foreach (var subThing in thing.childFolders.Concat(thing.childComponents))
{
var foundSub = FindThing(subThing, name);
if (foundSub != null)
return foundSub;
}
return null;
}
class RootObject
{
public Thing data { get; set; }
}
class Thing
{
public int id { get; set; }
public string name { get; set; }
public List<Thing> childFolders { get; set; } = new List<Thing>();
public List<Thing> childComponents { get; set; } = new List<Thing>();
}
And using it:
var obj = JsonConvert.DeserializeObject<RootObject>(jsonString);
var result = FindThing(obj.data, "route3");