Removing object using JArrayItem.Remove("value") not working - c#

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

Related

Getting error Newtonsoft.Json.Linq.JProperty cannot have multiple values when adding JToken

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

How to empty a JObject array in C#

I have the following json
{
"audit_date": "2020-05-13T11:27:10.3187798Z",
"client_uuid": "2fd77dd8-ed76-4bba-b0e1-5cda454c8d6e",
"audit_entry": {
"where_uri": "test.com/dataservice/apps/171f0841-825b-4964-8f8c-0869650f14a6",
"why_uri": "test.com/dataservice/reference/reasons_for_change/61acc173-7168-4ae5-9f04- afa228941f8b",
"who_uri": "test.com/securityservice/users/4977dae1-a307-425f-980c-53413fef1b0f",
"when_audited": "2018-11-13T20:20:39+00:00",
"what_uri": "test.com/dataservice/study_subjects/1bc67a71-8549-4ab8-9dd9-e44238198860",
"what_changed": [
{
"attribute_name": "birth_year",
"attribute_value": "1969",
"attribute_change": null
},
{
"attribute_name": "subject_reference",
"attribute_value": "TEST-WOO3444",
"attribute_change": null
}
]
}
}
But I want to empty the nest array "what_changed"
So I need the output to be
{
"audit_date": "2020-05-13T11:27:10.3187798Z",
"client_uuid": "2fd77dd8-ed76-4bba-b0e1-5cda454c8d6e",
"audit_entry": {
"where_uri": "test.com/dataservice/apps/171f0841-825b-4964-8f8c-0869650f14a6",
"why_uri": "test.com/dataservice/reference/reasons_for_change/61acc173-7168-4ae5-9f04-afa228941f8b",
"who_uri": "test.com/securityservice/users/4977dae1-a307-425f-980c-53413fef1b0f",
"when_audited": "2018-11-13T20:20:39+00:00",
"what_uri": "test.com/dataservice/study_subjects/1bc67a71-8549-4ab8-9dd9-e44238198860",
"what_changed": []
}
}
I have written the following code
JObject jObj = JObject.Parse(jsonText);
jObj["audit_entry"]["what_changed"] = null;
string json = jObj.ToString(Formatting.None);
but this makes the field null rather than empty array.
I have also tried
JObject jObj = JObject.Parse(jsonText);
jObj["audit_entry"]["what_changed"] = "";
string json = jObj.ToString(Formatting.None);
but that still doesn't give an empty array.
I also tried using the Array.Clear() method, but this is a JObject array rather than a normal array.
Arrays are represented by the JArray type, not JObject. Cast the value of "what_changed" to the proper type and use the methods you need. Eg:
JObject jObj = JObject.Parse(jsonText);
JArray changed=(JArray)(jObj["audit_entry"]["what_changed"]);
changed.Clear();
Working with JSON elements is rather unusual though. It's typically a lot easier to deserialize JSON strings into strongly typed objects, modify them as needed and then serialize them back to a string.
Generating the necessary DTOs can be done easily in Visual Studio by selecting Paste Special > Paste JSON as Classes from the Edit menu
I can see several possibilities...
1.- instead of clearing the array, create a new one, an empty one. This does not solve the problem but it is a work around.
2.- using newtonsoft (a nuget package that you can download), you may be able to find different utilities there.
2.1.- Instead of parsing with JObject, parse with JArray, and then use Clear:
https://www.newtonsoft.com/json/help/html/ParseJsonArray.htm
https://www.newtonsoft.com/json/help/html/M_Newtonsoft_Json_Linq_JArray_Clear.htm.
2.2.- use Remove, for this you need the property name, so you need to iterate within the array (a foreach), getting the name of the property, and delete one by one.
https://www.newtonsoft.com/json/help/html/M_Newtonsoft_Json_Linq_JObject_Remove.htm
You need to cast it to a JArray first, then you can use its Clear() method;
((JArray)(jObj["audit_entry"]["what_changed"])).Clear();
Alternatively, you could simply create a new JArray in place of the old one.
jObj["audit_entry"]["what_changed"] = new JArray();
Try this :
JObject jObj = JObject.Parse(jsonText);
JToken jToken = jObj["audit_entry"]["what_changed"];
jToken.Replace(JToken.Parse("[]"));
string json = jObj.ToString(Formatting.None);

Replace Json properties with NewtonSoft

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");
}

How to get the __type key in json to be first for DataContractJsonSerializer

[{"id":"PROCESS_ROOT_NODE","text":"TEMPLATE - 3333(2)","icon":"fa fa-list fa-color-graylt","li_attr":{"id":"PROCESS_ROOT_NODE","__type":"li_attr:#SomeNamespace.JsonDataContractClasses","class":" ps_node_li "}}]
I slimmed the object down alot.
Basically when the '__type' is not in the first position, before 'id'. The deserialize will throw an error.
I have all the DataContract stuff setup correctly, with known types.
I've tested in a console app, serializing, then taking that string back thru the deserialize and it works perfectly. The only difference is the location of the '__type'. This is a known MS issue.
Documented at https://msdn.microsoft.com/en-us/library/bb412170(v=vs.110).aspx
Tried a string replace, which does work. and the DataContractJsonSerializer did not care if '__type' key was in there twice.
content = content.Replace("\"li_attr\":{", "\"li_attr\":{\"__type\":\"li_attr:#Payce.Common.AnonymousClasses.JsonDataContractClasses\",");
Just looking for the best way to move the __type to the first position.
You can use Json.Net to manipulate your json
var jArr = JArray.Parse(jsonstring);
var attrs = jArr.Select(x => x["li_attr"]).ToList();
attrs.ForEach(attr =>
{
var type = attr["__type"].Parent;
type.Remove();
(attr as JObject).AddFirst(type);
});
var newjson = jArr.ToString(Newtonsoft.Json.Formatting.Indented);
Output of this code is
[
{
"id": "PROCESS_ROOT_NODE",
"text": "TEMPLATE - 3333(2)",
"icon": "fa fa-list fa-color-graylt",
"li_attr": {
"__type": "li_attr:#SomeNamespace.JsonDataContractClasses",
"id": "PROCESS_ROOT_NODE",
"class": " ps_node_li "
}
}
]
But I would recommend to use Json.Net all the way instead of just converting your json to the desired format.
Besides the string replacement.
I used an answer from Dave R - stack overflow
Use the JSON.stringify(obj, replacer array) replacer method.
var json = JSON.stringify(o, ['__type', 'id', 'parent', 'text', 'type', 'children', 'data', 'li_attr', 'a_attr', 'state', 'class', 'descr', 'display_priority', 'action_area_id', 'action_user_type_id']);
Its a little bit of a pain to list all the keys, but it also acts like a filter, so i only return what i need as well.
And since i put '__type' first, in all the objects and sub objects, this key was listed first after the stringify.

Getting the error "Cannot add or remove items from Newtonsoft.Json.Linq.JProperty" in Json.net

So I'm trying to control deserialization by reading a json object as a JObject, deleting some fields, and then deserializing it again to my target object using Json.Net. The problem is, any time I try to delete a field, I get the error:
An unhandled exception of type 'Newtonsoft.Json.JsonException'
occurred in Newtonsoft.Json.dll
Additional information: Cannot add or remove items from
Newtonsoft.Json.Linq.JProperty.
Here's my (simplified, but still causing the error) code:
JToken token = (JToken)JsonConvert.DeserializeObject(File.ReadAllText(fileName));
foreach (JToken inner in token["docs"])
{
if (inner["_id"] != null)
inner["_id"].Remove();
MyObject read = new MyObject();
JsonConvert.PopulateObject(inner.ToString(), read);
Values.Add((MyObject)JsonConvert.DeserializeObject(inner.ToString(), typeof(MyObject)));
}
The json is a very large file where the docs array contains many elements as follows (again simplified for clarity):
{
"docs": [
{
"Time": "None",
"Level": 1,
"_id": "10208"
},
{
"Time": "None",
"Level": 1,
"_id": "10209"
}
]
}
Alternatively if there's a better way to deserialize JSON to a specific type, but still ignoring additional fields, that would be a fine alternative.
Assuming Values is a List<MyObject> and your MyObject class looks like this:
class MyObject
{
public string Time { get; set; }
public int Level { get; set; }
}
you can replace all that code with the following to get the result you want:
string json = File.ReadAllText(fileName);
Values = JToken.Parse(json)["docs"].ToObject<List<MyObject>>();
This works because Json.Net will ignore missing properties by default. Since the MyObject class does not contain an _id property to deserialize into, you don't need to jump through hoops trying to remove it from the JSON.
Explanation of why Remove() didn't work
JToken.Remove() removes a JToken from its parent. It is legal to remove a JProperty from its parent JObject, or to remove a child JToken from a JArray. However, you cannot remove the value from a JProperty. A JProperty must always have exactly one value.
When you ask for token["_id"] you get back the value of the JProperty called _id, not the JProperty itself. Therefore you will get an error if you try to call Remove() on that value. To make it work the way you are doing, you'd need to use Parent like this:
if (inner["_id"] != null)
inner["_id"].Parent.Remove();
This says "Find the property whose name is _id and give me the value. If it exists, get that value's parent (the property), and remove it from its parent (the containing JObject)."
A more straightforward way to do it is to use the Property() method to access the property directly. However, this method is only available on JObject, not JToken, so you would either need to change the declaration of inner to a JObject or cast it:
foreach (JObject inner in token["docs"].Children<JObject>())
{
JProperty idProp = inner.Property("_id");
if (idProp != null)
idProp.Remove();
...
}
Lastly, as mentioned in the comments, if you're using C# 6 or later you can shorten the code a bit using the null-conditional operator:
inner.Property("_id")?.Remove();
Based on brillian answer from Brian, you can do simply this in your case:
var inner_id = inner["_id"] as JProperty;
if (inner_id != null)
inner_id.Remove();

Categories

Resources