Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support ability to DeepClone a JsonNode in order to recursively create a copy of all children nodes #54019

Closed
husains opened this issue Jun 10, 2021 · 7 comments
Labels
api-suggestion Early API idea and discussion, it is NOT ready for implementation area-System.Text.Json needs-further-triage Issue has been initially triaged, but needs deeper consideration or reconsideration
Milestone

Comments

@husains
Copy link

husains commented Jun 10, 2021

Background and Motivation

Writable JSON DOM API's are available as part of the .NET 6 private preview. However, the current API's do not support an ability to perform a deep clone (i.e. create a recursive copy of all children nodes).

Including support for a DeepClone API enables the ability to take an input JsonNode, apply transforms or filters, and copy over subsets of the source object into a new JsonNode (which can then be used for further manipulation or for serialization).

JSON.NET has an analogous API with the same name (DeepClone). An example using existing JSON.NET API's is here.

A workaround, in the absence of a DeepClone API is to serialize the JsonNode into a string, and re-convert the same into a new JsonNode. However, a built-in DeepClone API can likely be made to perform better, and avoid the interim conversion into a string (and associated allocations).

Proposed API

namespace System.Text.Json.Nodes
{
      public class JsonNode {
+        public JsonNode DeepClone();
      }
}

Usage Examples

Below is an example of how the new DeepClone API can be used

// some lines of code here

        public string FilterAndSerialize(JsonObject data)
        {
            JsonObject newData = new JsonObject();
            newData["Product"] = new JsonObject();
            newData["Product"]["Eligibility"] = new JsonObject();

            var predicates = new JsonArray();
            newData["Product"]["Eligibility"]["Predicates"] = predicates;

            foreach (var item in data["Product"]["Eligibility"]["Predicates"].AsArray())
            {
                if (item["Type"].GetValue<string>().Equals("ClaimsPredicate"))
                {
                    predicates.Add(item.DeepClone());
                }
            }

            return Serialize(newData);
        }

Alternative Designs

Use combination of ToJsonString & Parse APIs. However, performance would likely suffer due to the intermediate string conversion.

@husains husains added the api-suggestion Early API idea and discussion, it is NOT ready for implementation label Jun 10, 2021
@dotnet-issue-labeler dotnet-issue-labeler bot added area-System.Text.Json untriaged New issue has not been triaged by the area owner labels Jun 10, 2021
@ghost
Copy link

ghost commented Jun 10, 2021

Tagging subscribers to this area: @eiriktsarpalis, @layomia
See info in area-owners.md if you want to be subscribed.

Issue Details

Background and Motivation

Writable JSON DOM API's are available as part of the .NET 6 private preview. However, the current API's do not support an ability to perform a deep clone (i.e. create a recursive copy of all children nodes).

Including support for a DeepClone API enables the ability to take an input JsonNode, apply transforms or filters, and copy over subsets of the source object into a new JsonNode (which can then be used for further manipulation or for serialization).

JSON.NET has an analogous API with the same name (DeepClone). An example using existing JSON.NET API's is here.

A workaround, in the absence of a DeepClone API is to serialize the JsonNode into a string, and re-convert the same into a new JsonNode. However, a built-in DeepClone API can likely be made to perform better, and avoid the interim conversion into a string (and associated allocations).

Proposed API

namespace System.Text.Json.Nodes
{
      public class JsonNode {
+        public JsonNode DeepClone();
      }
}

Usage Examples

Below is an example of how the new DeepClone API can be used

// some lines of code here

        public string FilterAndSerialize(JsonObject data)
        {
            JsonObject newData = new JsonObject();
            newData["Product"] = new JsonObject();
            newData["Product"]["Eligibility"] = new JsonObject();

            var predicates = new JsonArray();
            newData["Product"]["Eligibility"]["Predicates"] = predicates;

            foreach (var item in data["Product"]["Eligibility"]["Predicates"].AsArray())
            {
                if (item["Type"].GetValue<string>().Equals("ClaimsPredicate"))
                {
                    predicates.Add(item.DeepClone());
                }
            }

            return Serialize(newData);
        }

Alternative Designs

Use combination of ToJsonString & Parse APIs. However, performance would likely suffer due to the intermediate string conversion.

Author: husains
Assignees: -
Labels:

api-suggestion, area-System.Text.Json, untriaged

Milestone: -

@GrabYourPitchforks
Copy link
Member

GrabYourPitchforks commented Jun 10, 2021

What would happen in the following scenario?

public class Person
{
    public string Name { get; set; }
    public int Age { get; set; }
}

Person p = new Person() { Name = "Andy", Age = 50 };
JsonNode original = JsonValue.Create(p);
Person p2 = original.GetValue<Person>();
JsonNode cloned = original.DeepClone();
Person p3 = cloned.GetValue<Person>();

@husains
Copy link
Author

husains commented Jun 11, 2021

What would happen in the following scenario?

public class Person
{
    public string Name { get; set; }
    public int Age { get; set; }
}

Person p = new Person() { Name = "Andy", Age = 50 };
JsonNode original = JsonValue.Create(p);
Person p2 = original.GetValue<Person>();
JsonNode cloned = original.DeepClone();
Person p3 = cloned.GetValue<Person>();

If we need to convert a .NET object into one of the Json DOM API objects, then I would have expected to use JsonObject.Create(p), or JsonObject.FromObject(p), or new JsonObject(p). Unclear about why JsonValue.Create is being used here. Could you please clarify the same? Not sure of the intention of the current JsonValue.Create API.

I was expecting JsonValue to only be used for primitive Json data types like string, number, and possibly support some conversions into other primitives like dates/timestamps.

@husains husains changed the title Support ability to DeepClone any JsonNode to recursively create a copy of all children nodes Support ability to DeepClone a JsonNode in order to recursively create a copy of all children nodes Jun 11, 2021
@GrabYourPitchforks
Copy link
Member

My comment was intended to spark discussion of "what happens if the DOM contains an object which is not cloneable?" JsonNode can contain any arbitrary data, not just primitives. The behavior of any DeepClone method would need to be well-defined when it sees such nodes. Does it fail, make a shallow clone, try calling ICloneable.Clone, serialize + deserialize, or something else? And if a call to GetValue<Person> succeeds before the call to DeepClone, is it expected to succeed on the resulting cloned graph?

I'm not really advocating any particular course of action. If "it throws an exception" is the desired behavior - great! :)

The main point was that I think this proposal needs to define the expected behavior even in the face of all of these edge cases.

@eiriktsarpalis
Copy link
Member

cc @steveharter

@eiriktsarpalis eiriktsarpalis added needs-further-triage Issue has been initially triaged, but needs deeper consideration or reconsideration and removed untriaged New issue has not been triaged by the area owner labels Jul 16, 2021
@eiriktsarpalis eiriktsarpalis added this to the 7.0.0 milestone Jul 16, 2021
@steveharter
Copy link
Member

As shown above, JsonValue can contain a direct reference to an arbitrary CLR type. This is to avoid an extra serialization and deserialization step.

So if a Person instance already exists, and we required creation of a JsonObject then the steps to serialize would be:

  1. Serialize Person to JSON
  2. Deserialize that JSON to JsonObject
  3. Serialize JsonObject to JSON

instead of just:

  1. Serialize Person to JSON

So I think the most obvious DeepClone semantics would be to:

  • Serialize node to JSON
  • Deserialize that JSON to JsonNode which will lose any arbitrary CLR instances which will thus be represented by a JsonObject (in the Person example), JsonArray or JsonValue

@steveharter steveharter removed their assignment Sep 23, 2021
@eiriktsarpalis
Copy link
Member

Closing as duplicate of #56592.

@ghost ghost locked as resolved and limited conversation to collaborators Nov 14, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
api-suggestion Early API idea and discussion, it is NOT ready for implementation area-System.Text.Json needs-further-triage Issue has been initially triaged, but needs deeper consideration or reconsideration
Projects
None yet
Development

No branches or pull requests

4 participants