No idea where to begin with this, so I don't have any sample code.
I need to change the name of a property in a json document.
var json = (#"{""id"":""12"",
""title"":""My Title"",
""Chunks"":[
{
""id"":""137"",
""title"":""Title"",
""description"":""null"",
""selections"":[
{
""id"":""169"",
""title"":""Choice"",
""sort_order"":""null"",
""questions"":[
]
}
]
}
]
}
}
}");
I need to change the "id" that's got the value of 12 to "document_id" and leave the other ids alone. Are there any C# libraries like NewtonSoft that allow you to change the property rather than the property value. Seems like a common scenario but I haven't seen anything close to what I'm trying to do. I suppose I could convert the json to a string and do a replace, but that doesn't seem very elegant.
An approach using Newtonsoft.Json.Linq.JObject would look something like:
var obj = JObject.Parse(json);
obj["document_id"] = obj["id"]; // create new property called "document_id"
obj.Remove("id"); // remove the "id" property
Console.WriteLine(obj);
Also note that your JSON is not valid. It has two extra } at the end.
Assuming you would want to replace all the keys when there could be more than one node with key as "id" and value "12", you could use Linq to identify Tokens with Key "Id" and Value "12" and then use Add/Remove methods for creating a new node with different name.
For example,
JToken node = JToken.Parse(json);
var jObjectsWithTitle = node
.SelectTokens("$..*")
.OfType<JObject>()
.Where(x => x.Property("id") != null && Convert.ToInt32(x.Property("id").Value) == 12);
foreach(var item in jObjectsWithTitle)
{
item.TryGetValue("id",out var currentValue);
item.Add("document_id",currentValue);
item.Remove("id");
}
Related
I am trying to remove an object from an existing JArray, but when I run the method to delete the property from the JArray, the property still exists.
I tried simply using the code below
JObject rss1 = JObject.Parse(File.ReadAllText("online.json"));
JObject uList = (JObject)rss1["uList"];
JArray newOnline = (JArray)uList["online"];
newOnline.Remove("value");
The code above is almost directly copied and pasted from the Newtonsoft.Json documentation on modifying Json, but only changed to remove the new item instead of adding it. I've tried other post's solutions, but none of them work. Adding the field works as expected, but trying to remove it does not work whatsoever.
Below is the JSON file's contents
{
"uList": {
"placeHolder": "placeHolderValue",
"online": [
"value"
]
}
}
Below is the expected JSON output of the code being ran
{
"uList": {
"placeHolder": "placeHolderValue",
"online": [
]
}
}
And, below, is the actual output of the code being ran
{
"uList": {
"placeHolder": "placeHolderValue",
"online": [
"value"
]
}
}
Am I doing something wrong and not realizing it?
The problem you've got is that you don't actually have a string in your array, you have a JValue. The JArray.Remove method accepts a JToken, but the JToken produced by implicitly casing your string to a JValue is not equal to the one being removed.
There's one of two solutions that come to mind:
Find the item in the array and then pass that to the remove method:
JToken item = newOnline.FirstOrDefault(arr => arr.Type == JTokenType.String && arr.Value<string>() == "value");
newOnline.Remove(item);
Use the .Remove method of the array item to remove it from the array:
JToken item = newOnline.FirstOrDefault(arr => arr.Type == JTokenType.String && arr.Value<string>() == "value");
item?.Remove();
You can also remove JToken by querying value string present in online JArray.
Parse string to JObject:
JObject rss1 = JObject.Parse(File.ReadAllText("online.json"));
Now remove particular token using below query.
rss1.SelectTokens("$.uList.online[?(# == 'value')]").FirstOrDefault()?.Remove();
Try online
I have the following structure of additional information where I need to update the value of one of the tokens in the structure. The data is an array of JTokens with a parent called 'additionalFields' as follows:
{{"additionalFields":
[
{ "name": "NAME1", "value": "VALUE1" },
{ "name": "NAME2", "value": "VALUE2" },
{ "name": "NAME3", "value": "VALUE3" },
{ "name": "NAME4", "value": "VALUE4" }
]}
I'm trying to update the value of one of the tokens e.g. to change VALUE1 to VALUE10.
Once I have located the token I need to update my code removes it as follows.
additionalField.Remove();
I then create a new token to replace the one I have removed (containing the new value) using the following functions.
public static JToken CreateNewToken(string name, string value)
{
var stringToken = CreateNewStringToken(name, value);
var token = JToken.Parse(stringToken);
return (JToken) token;
}
private static string CreateNewStringToken(string name, string value)
{
return $"{{\"name\":\"{name}\",\"value\":\"{value}\"}}";
}
I then add the new token as follows.
additionalFields.AddAfterSelf(updatedToken);
Putting it all together we have the following
foreach (var additionalField in additionalFields)
{
//is this the key we are looking for?
var keyToken = additionalField.First;
if (keyToken?.First == null) continue;
if (string.Equals(keyToken.First.ToString(), "newname", StringComparison.CurrentCultureIgnoreCase))
{
//remove the current token
additionalField.Remove();
//add the updated token
var updatedToken = CreateNewToken("newname", "newvalue");
additionalFields.AddAfterSelf(updatedToken); <-- error occurs here!!
}
}
However after adding the token I'm getting the following error
Newtonsoft.Json.Linq.JProperty cannot have multiple values
I can see in the debugger that the token has been removed (as the token.Count is reduced by 1) so cannot understand why I'm getting an error adding the replacement token.
I was able to reproduce your problem here: https://dotnetfiddle.net/JIVCVB
What is going wrong
additionalFields refers to the JArray of JObjects containing name and value JProperties. You are looping through this JArray to try to find the first JObject having a name property with a certain value, and when you find it you attempt to replace the JObject with a whole new JObject. You successfully remove the old JObject from the JArray, but when you are doing AddAfterSelf to insert the new JObject, you are referencing additionalFields (plural) not additionalField (singular). Recall that additionalFields is the JArray. So you are saying that you want to add the new JObject after the array. The array's parent is a JProperty called additionalFields. A JProperty can only have one value, so AddAfterSelf fails with the error you see.
How to fix your code
I think what you intended to do was additionalField.AddAfterSelf(updatedToken). However, this, too, will fail, for a different reason: you already removed the additionalField from the JArray at that point, so it no longer has a parent context. You would need to AddAfterSelf before you remove the item you are trying to insert after. If you fix that, you still have another problem: your loop doesn't break out after you've done the replacement, so then you will get an error about modifying the collection while looping over it.
Here is the relevant section of code with the corrections:
if (string.Equals(keyToken.First.ToString(), "NAME1", StringComparison.CurrentCultureIgnoreCase))
{
//add the updated token
var updatedToken = CreateNewToken("newname", "newvalue");
additionalField.AddAfterSelf(updatedToken);
//remove the current token
additionalField.Remove();
// we found what we were looking for so no need to continue looping
break;
}
Fiddle: https://dotnetfiddle.net/KcFsZc
A simpler approach
You seem to be jumping through a lot of hoops to accomplish this task. Instead of looping, you can use FirstOrDefault to find the object you are looking for in the array. Once you've found it, you don't need to replace the whole object; you can just update the property values directly.
Here's how:
var rootObject = JToken.Parse(json);
// Get a reference to the array of objects as before
var additionalFields = rootObject["additionalFields"];
// Find the object we need to change in the array
var additionalField = additionalFields.FirstOrDefault(f =>
string.Equals((string)f["name"], "NAME1", StringComparison.CurrentCultureIgnoreCase);
// if the object is found, update its properties
if (additionalField != null)
{
additionalField["name"] = "newname";
additionalField["value"] = "newvalue";
}
Working demo: https://dotnetfiddle.net/ZAKRmi
I am working with Jsons which I don't know their structure in advanced. Just for example:
{
"OrganizationData": {
"Org1": {
"Name": "Rega And Dodli",
"EmployessNum": "100000000"
},
"Org2": {
"Name": "Sami And Soso",
"EmployessNum": "2"
}
}
}
I'm currently getting values by using the SelectToken method to which I can pass a key with a sub key like this:
var token = myJObject.SelectToken("OrganizationData.Org1")
This works fine. Now I want to add a new entry to the JSON using a string like that, something like:
myJObject.Add("OrganizationData.Org3", myValueJson);
but calling add like that directly just adds a new key to the json called "OrganizationData.Org3" and not creating a new sub key called "Org3" inside "OrganizationData" like the current "Org1" and "Org2".
How can I add a new value with a delimited string like needed?
JSON doesn't have subkeys or delimited keys. OrganizationData.Org1 is a LINQ to JSON search expression, not a subkey.
To add Org3 you can use one of the many ways available to modify a JSON object. You can add a child element to OrganizationData or a sibling to one of the other Org nodes.
To add a child element to a node, you could use .SelectToken("OrganizationData") if you don't already have a reference to it, and use JObject.Add to add the new node. You'll have to cast the result to JObject first, as SelectToken returns a JToken. If there's a chance that OrganizationData is an array, you'll have to check the type too.
For example:
var token = myJObject.SelectToken("OrganizationData");
if(token is JObject orgObj)
{
orgObj.Add("Org3",myValueJson);
}
Working with unknown paths
The same thing works if the path is specified at runtime. In this case, all that's needed is to separate the last part from the rest of the path, perhaps using String.LastIndexOf`:
var lastDot=path.LastIndexOf('.');
if (lastDot<0)
{
//Oops! There's no dot. What do we do now?
}
var parent=path.Substring(0,lastDot);
var key=path.Substring(lastDot+1);
var token = myJObject.SelectToken(parent);
if(token is JObject orgObj)
{
orgObj.Add(key,myValueJson);
}
You'll have to decide what to do if the path contains no dot. Is this an invalid path? Or should a new object be added under the root object?
I got a json with some nested object, for example:
{
"OrganizationData": {
"Org1": {
"Name": "Rega And Dodli",
"EmployessNum": "100000000"
},
"Org2": {
"Name": "Sami And Soso",
"EmployessNum": "2"
}
}
}
I want to get for example the value for the "Name" of "Org1".
I know I can do something like this:
var rss = JObject.Parse(mystring);
var value = rss["OrganizationData"]["Org1"]["Name"];
My question is if it's possible to replace the multiple indexers part (["OrganizationData"]["Org1"]["Name"]) with a single indexer (or something else which is not an indexer) with a single string which is composed of all 3 keys and still get the same value?
For example something like:
var rss = JObject.Parse(mystring);
var value = rss["OrganizationData:Org1:Name"];
I remember there's something with ":" but this one I tried in the example above did not work.
You can use the JObject.SelectToken method, using period (.) as the property path delimiter. For example:
var value = rss.SelectToken("OrganizationData.Org1.Name");
I have the following json file
{"fields":[
{
"status":"active",
"external_id":"title",
"config":{},
"field_id":11848871,
"label":"Title",
"values":[
{
"value":"Test Deliverable"
}
],
"type":"text"
},{
"status":"active",
"external_id":"client-name",
"config":{},
"field_id":12144855,
"label":"Client Name",
"values":[
{
"value":"Chcuk Norris"
}
],
"type":"text"
}}
And I want to select the value of the field that has its external_id = "title" for example, I'm using Json.Net and already parsed the object. How do i do this using lambda or linq on the Json object, I trird something like this
JObject o = JObject.Parse(json);
Title = o["fields"].Select(q => q["extenral_id"].Values[0] == "title");
Which is not event correct in terms of syntax. I'm not very proficient in Lambda or Linq thought its been there for a while. Appreciate the help
Thanks
Yehia
Or you can do this:
string json = "{\"fields\":[{\"status\":\"active\",\"external_id\":\"title\",\"config\":{},\"field_id\":11848871,\"label\":\"Title\",\"values\":[{\"value\":\"Test Deliverable\"}],\"type\":\"text\"},{\"status\":\"active\",\"external_id\":\"client-name\",\"config\":{},\"field_id\":12144855,\"label\":\"Client Name\",\"values\":[{\"value\":\"Chcuk Norris\"}],\"type\":\"text\"}]}";
JObject obj = JObject.Parse(json);
JArray arr = (JArray)obj["fields"];
var externalIds = arr.Children().Select(m=>m["external_id"].Value<string>());
externalIds is a IEnumerable array of string
Or you can chain it together and select the object in one line:
var myVal = JObject.Parse(json)["fields"].Children()
.Where(w => w["external_id"].ToString() == "title")
.First();
From there you can append whatever selector you want ie if you want the external_id value then append ["external_id"].ToString() to the end of the first() selector.
Build classes for your objects first, then parse them so you can access them correctly and its no anonymous type anymore.
For example this classes:
class MyJson {
public List<MyField> fields {get;set;}
}
class MyField {
public string status {get;set;}
public string external_id {get;set;}
// and so on
}
Then use that class for parsing the json (don't know the exact syntax right now) like this:
var o = Json.Parse(json, typeof(MyJson));
And then you can select your data easily with Linq and have intellisense in VS (or similar dev env):
var myData = o.fields.Where(q=>q.external_id=="title");
If you had your JSON converted to objects (basically what Marc suggested), the LINQ query would look something like:
o.fields.Single(q => q.external_id == "title")
But if you don't want to do that, you have to access the values by string keys. If you don't want to convert the type of the value, you can simply use indexing (["key"]). But if you want to convert the type, you can use Value<Type>("key"). Putting it together, the whole query might be:
o["fields"].Single(q => q.Value<string>("external_id") == "title")