JArray.Remove(JToken) does not delete

ronald.js.cr picture ronald.js.cr · Dec 15, 2017 · Viewed 8.5k times · Source

I have a JObject with a JSON like it:

{"name" : "user1", "groups" : ["group 1", "group2"]}

I would like to delete one group by the name. So I have a code like this:

JObject userJson = JObject.Parse(user);

JArray groups = userJson["groups"] as JArray;
JToken group = new JValue (groupName);

groups.Remove(group);

However the method JArray.remove(Jtoken item) return false (that means that it couldn't be deleted). Here the information:

https://www.newtonsoft.com/json/help/html/M_Newtonsoft_Json_Linq_JArray_Remove.htm

So tried to set the parameter like JToken group = new JValue (groupName); JToken group = new JValue (groupName); as JToken and JValue group = new JValue (groupName); But it doesnt work :S

This guy explains JToken hierarchy very good but I don't know what I'm not doing right.

https://stackoverflow.com/a/38560188/8849646

Answer

dbc picture dbc · Dec 15, 2017

The basic issue is that the JToken hierarchy is doubly-connected graph. That is, each token knows its Parent and each parent knows its Children. Indeed, if you add a token that already has a parent to a parent, it gets cloned, as explained here.

Thus, since every token knows its parent, when you try to remove a token from a parent, Json.NET might do one of two things:

  1. It might remove the child from the parent if the child actually belongs to the parent (using reference equality), OR
  2. It might remove the child from the parent if the child has the save values as some child of the parent.

And, in fact, Json.NET chooses the former option. Jarray.Remove(JToken item) calls JContainer.RemoveItem() which calls JArray.IndexOfItem() to determine the index of the item to remove. This method, in turn, uses reference equality:

internal override int IndexOfItem(JToken item)
{
    return _values.IndexOfReference(item);
}

Since your JToken group = new JValue (groupName) does not belong to the JArray groups, it is not removed.

So, what are your options to remove a JSON array item by value? You could:

  • Search using LINQ:

    groups.Where(i => i.Type == JTokenType.String && (string)i == groupName).ToList().ForEach(i => i.Remove());
    
  • Search using JTokenEqualityComparer, which can be used to search for complex objects as well as primitive values:

    var comparer = new JTokenEqualityComparer();
    groups.Where(i => comparer.Equals(i, group)).ToList().ForEach(i => i.Remove());
    
  • Search using SelectTokens():

    userJson.SelectTokens(string.Format("groups[?(@ == '{0}')]", groupName)).ToList().ForEach(i => i.Remove());
    

    SelectTokens() supports the JSONPath query syntax which enables searching though arrays for matching items.

Finally, a note about the documentation for RemoveItem(). It states (italics added):

JArray.Remove Method (JToken)

Removes the first occurrence of a specific object from the JArray.

Since we have seen that a token that has a parent is cloned when added to a parent, it seems there can only ever be one occurrence of any token within a given parent. Yet the documentation seems to imply otherwise; I would hazard a guess that this particular documentation sentence is obsolete and dates from some much earlier version of Json.NET where the same token could appear within a parent multiple times.