From ef34cbfdff551407cc2cda669451275f946fd871 Mon Sep 17 00:00:00 2001 From: m-nash <64171366+m-nash@users.noreply.github.com> Date: Mon, 22 Sep 2025 13:22:34 -0700 Subject: [PATCH 1/6] Prepare System.ClientModel for release --- sdk/core/System.ClientModel/CHANGELOG.md | 9 +- .../src/System.ClientModel.csproj | 2 +- .../System.ClientModel/src/docs/JsonPatch.md | 107 ++++++++++++++++++ 3 files changed, 110 insertions(+), 8 deletions(-) create mode 100644 sdk/core/System.ClientModel/src/docs/JsonPatch.md diff --git a/sdk/core/System.ClientModel/CHANGELOG.md b/sdk/core/System.ClientModel/CHANGELOG.md index c57e96f99281..e7efe96eee86 100644 --- a/sdk/core/System.ClientModel/CHANGELOG.md +++ b/sdk/core/System.ClientModel/CHANGELOG.md @@ -1,17 +1,12 @@ # Release History -## 1.7.0-beta.1 (Unreleased) +## 1.7.0 (2025-09-22) ### Features Added - Added `ClientConnection` constructor, accepting credentials and metadata. -### Breaking Changes - -### Bugs Fixed - -### Other Changes - +- Added `JsonPatch` which allows for applying JSON Patch operations to JSON documents. ## 1.6.1 (2025-08-20) ### Features Added diff --git a/sdk/core/System.ClientModel/src/System.ClientModel.csproj b/sdk/core/System.ClientModel/src/System.ClientModel.csproj index 3bbd0d9bda8b..cccb79dad5a0 100644 --- a/sdk/core/System.ClientModel/src/System.ClientModel.csproj +++ b/sdk/core/System.ClientModel/src/System.ClientModel.csproj @@ -2,7 +2,7 @@ Contains building blocks for clients that call cloud services. - 1.7.0-beta.1 + 1.7.0 1.6.1 enable diff --git a/sdk/core/System.ClientModel/src/docs/JsonPatch.md b/sdk/core/System.ClientModel/src/docs/JsonPatch.md new file mode 100644 index 000000000000..f21e717bed51 --- /dev/null +++ b/sdk/core/System.ClientModel/src/docs/JsonPatch.md @@ -0,0 +1,107 @@ +# JsonPatch + +This is a new experimental type for applying JSON patches to a model that includes this as a property +or for dealing with raw UTF8 JSON directly. The examples in this document show using outside +of being attached to a model for simplicity. + +There are 5 main APIs for library users, `Set`, `Append`, `Get`, `Remove`, `SetNull`. Each of these have overloads for different +value types to get in and out of the JsonPatch object. + +All of these APIs take in `ReadOnlySpan` which should be UTF8 representation of a single target JSON Path following [RFC 9535](https://www.rfc-editor.org/rfc/rfc9535). +Even if a filter selector results in single target they are currently not supported such as `$.x.y[?@.name='foo']` where the array at y only contains one element with the name foo. + +This type is intended to follow [RFC 6902](https://www.rfc-editor.org/rfc/rfc6902) although currently does not have Move, Copy, or Test implemented. +The default `ToString` implementation will print in RFC 6902 format, but will take an +optional format string to print in other formats in the future. The only other format currently supported is `J` which will print the current model state +after applying the patches in RFC 8259 format. + +## Library user APIs + +### Set + +```c# +JsonPatch jp = new(); +jp.Set("$.x"u8, 5); +Console.WriteLine(jp); // [{"op":"add","path":"/x","value":5}] +Console.WriteLine(jp.ToString("J")); // {"x":5} +``` + +Set will project the json structure from the path so that users do not have to set each layer one by one + +```c# +JsonPatch jp = new(); +jp.Set("$.x.y[2].z"u8, 5); +Console.WriteLine(jp); // [{"op":"add","path":"/x","value":{"y":[null,null,{"z":5}]}}] +Console.WriteLine(jp.ToString("J")); // {"x":{"y":[null,null,{"z":5}]}} +``` + +The nulls are inserted to fill up to the index in the path if it doesn't exist yet. + +You can also pass json structures yourself to Set if you already have a json byte array + +```c# +JsonPatch jp = new(); +jp.Set("$.x"u8, "{\"y\":[null,null,{\"z\":5}]}"u8); +Console.WriteLine(jp); // [{"op":"add","path":"/x","value":{"y":[null,null,{"z":5}]}}] +Console.WriteLine(jp.ToString("J")); // {"x":{"y":[null,null,{"z":5}]}} +``` + +### Append + +Append will add to an existing array. + +```c# +JsonPatch jp = new("[1,2,3]"u8.ToArray()); +jp.Append("$"u8, 4); +Console.WriteLine(jp); // [{"op":"add","path":"/-","value":4}] +Console.WriteLine(jp.ToString("J")); // [1,2,3,4] +``` + +Append will also utilize json projection if the path doesn't exist yet. + +```c# +JsonPatch jp = new(); +jp.Append("$.x[1]", 1); +Console.WriteLine(jp); // [{"op":"add","path":"/x","value":[null,[1]]}] +Console.WriteLine(jp.ToString("J")); // {"x":[null,[1]]} +``` + +### Get + +Get allows a user to pull information from the patches that have been inserted so far or from the +original json used to construct the JsonPatch. + +Get allows you to pull primitive values as well as raw json. For the primitive values it has an overload GetNullableValue to allow for things like int?. + +```c# +JsonPatch jp = new("{\"x\":{\"y\":[null,null,{\"z\":5}]}}"u8.ToArray(); +int value = jp.GetInt32("$.x.y[2].z"u8); +Console.WriteLine(value); // 5 + +BinaryData json = jp.GetJson("$.x.y"); +Console.WriteLine(json); // [null,null,{"z":5}] +``` + +### Remove + +Remove deletes a path from the payload which is distinct from setting something to null. + +```c# +JsonPatch jp = new("{\"x\":{\"y\":[null,null,{\"z\":5}]}}"u8.ToArray(); +jp.Remove("$.x.y[1]"u8); +Console.WriteLine(jp); // [{"op":"remove","path","/x/y/1"}] +Console.WriteLine(jp.ToString("J")); // {"x":{"y":[null,{"z":5}]}} +``` + +### SetNull + +Sets explicit null at a specific json path. The reason this is separate from Set is in order to accept `null` we would need an +overload with object which introduces lots of failure cases where a library user might try to pass in a random object which +we cannot deal with generically. + +```c# +JsonPatch jp = new("{\"x\":{\"y\":[null,null,{\"z\":5}]}}"u8.ToArray(); +jp.SetNull("$.x.y[2]"u8); +Console.WriteLine(jp); // [{"op":"replace","path":"/x/y/2","value":null}] +Console.WriteLine(jp.ToString("J")); // {"x":{"y":[null,null,null]}} +``` From c897fa7a5bcc4dd4db69e58c0cf8a6fc4af14a27 Mon Sep 17 00:00:00 2001 From: m-nash <64171366+m-nash@users.noreply.github.com> Date: Mon, 22 Sep 2025 13:24:51 -0700 Subject: [PATCH 2/6] Update sdk/core/System.ClientModel/src/docs/JsonPatch.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- sdk/core/System.ClientModel/src/docs/JsonPatch.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/core/System.ClientModel/src/docs/JsonPatch.md b/sdk/core/System.ClientModel/src/docs/JsonPatch.md index f21e717bed51..31e8550afd2d 100644 --- a/sdk/core/System.ClientModel/src/docs/JsonPatch.md +++ b/sdk/core/System.ClientModel/src/docs/JsonPatch.md @@ -61,7 +61,7 @@ Append will also utilize json projection if the path doesn't exist yet. ```c# JsonPatch jp = new(); -jp.Append("$.x[1]", 1); +jp.Append("$.x[1]"u8, 1); Console.WriteLine(jp); // [{"op":"add","path":"/x","value":[null,[1]]}] Console.WriteLine(jp.ToString("J")); // {"x":[null,[1]]} ``` From f8b96f0bc76f12d8aadbec00750bbed3729a1762 Mon Sep 17 00:00:00 2001 From: m-nash <64171366+m-nash@users.noreply.github.com> Date: Mon, 22 Sep 2025 13:25:05 -0700 Subject: [PATCH 3/6] Update sdk/core/System.ClientModel/src/docs/JsonPatch.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- sdk/core/System.ClientModel/src/docs/JsonPatch.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/core/System.ClientModel/src/docs/JsonPatch.md b/sdk/core/System.ClientModel/src/docs/JsonPatch.md index 31e8550afd2d..c48f23e9297c 100644 --- a/sdk/core/System.ClientModel/src/docs/JsonPatch.md +++ b/sdk/core/System.ClientModel/src/docs/JsonPatch.md @@ -74,7 +74,7 @@ original json used to construct the JsonPatch. Get allows you to pull primitive values as well as raw json. For the primitive values it has an overload GetNullableValue to allow for things like int?. ```c# -JsonPatch jp = new("{\"x\":{\"y\":[null,null,{\"z\":5}]}}"u8.ToArray(); +JsonPatch jp = new("{\"x\":{\"y\":[null,null,{\"z\":5}]}}"u8.ToArray()); int value = jp.GetInt32("$.x.y[2].z"u8); Console.WriteLine(value); // 5 From 9724b50b77dc49a55a540f2ff4733b5df4b870ba Mon Sep 17 00:00:00 2001 From: m-nash <64171366+m-nash@users.noreply.github.com> Date: Mon, 22 Sep 2025 13:25:14 -0700 Subject: [PATCH 4/6] Update sdk/core/System.ClientModel/src/docs/JsonPatch.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- sdk/core/System.ClientModel/src/docs/JsonPatch.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/core/System.ClientModel/src/docs/JsonPatch.md b/sdk/core/System.ClientModel/src/docs/JsonPatch.md index c48f23e9297c..48b11b1dca95 100644 --- a/sdk/core/System.ClientModel/src/docs/JsonPatch.md +++ b/sdk/core/System.ClientModel/src/docs/JsonPatch.md @@ -87,7 +87,7 @@ Console.WriteLine(json); // [null,null,{"z":5}] Remove deletes a path from the payload which is distinct from setting something to null. ```c# -JsonPatch jp = new("{\"x\":{\"y\":[null,null,{\"z\":5}]}}"u8.ToArray(); +JsonPatch jp = new("{\"x\":{\"y\":[null,null,{\"z\":5}]}}"u8.ToArray()); jp.Remove("$.x.y[1]"u8); Console.WriteLine(jp); // [{"op":"remove","path","/x/y/1"}] Console.WriteLine(jp.ToString("J")); // {"x":{"y":[null,{"z":5}]}} From e999c8e03ddb432a8b2cf94a127ca5e07abb4962 Mon Sep 17 00:00:00 2001 From: m-nash <64171366+m-nash@users.noreply.github.com> Date: Mon, 22 Sep 2025 13:25:43 -0700 Subject: [PATCH 5/6] Update sdk/core/System.ClientModel/src/docs/JsonPatch.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- sdk/core/System.ClientModel/src/docs/JsonPatch.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/core/System.ClientModel/src/docs/JsonPatch.md b/sdk/core/System.ClientModel/src/docs/JsonPatch.md index 48b11b1dca95..7f3c9b37cf9c 100644 --- a/sdk/core/System.ClientModel/src/docs/JsonPatch.md +++ b/sdk/core/System.ClientModel/src/docs/JsonPatch.md @@ -89,7 +89,7 @@ Remove deletes a path from the payload which is distinct from setting something ```c# JsonPatch jp = new("{\"x\":{\"y\":[null,null,{\"z\":5}]}}"u8.ToArray()); jp.Remove("$.x.y[1]"u8); -Console.WriteLine(jp); // [{"op":"remove","path","/x/y/1"}] +Console.WriteLine(jp); // [{"op":"remove","path":"/x/y/1"}] Console.WriteLine(jp.ToString("J")); // {"x":{"y":[null,{"z":5}]}} ``` From e53bfd2f6feb60fcec163f7220a36398de8db724 Mon Sep 17 00:00:00 2001 From: m-nash <64171366+m-nash@users.noreply.github.com> Date: Mon, 22 Sep 2025 13:25:52 -0700 Subject: [PATCH 6/6] Update sdk/core/System.ClientModel/src/docs/JsonPatch.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- sdk/core/System.ClientModel/src/docs/JsonPatch.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/core/System.ClientModel/src/docs/JsonPatch.md b/sdk/core/System.ClientModel/src/docs/JsonPatch.md index 7f3c9b37cf9c..e2257f641bec 100644 --- a/sdk/core/System.ClientModel/src/docs/JsonPatch.md +++ b/sdk/core/System.ClientModel/src/docs/JsonPatch.md @@ -100,7 +100,7 @@ overload with object which introduces lots of failure cases where a library user we cannot deal with generically. ```c# -JsonPatch jp = new("{\"x\":{\"y\":[null,null,{\"z\":5}]}}"u8.ToArray(); +JsonPatch jp = new("{\"x\":{\"y\":[null,null,{\"z\":5}]}}"u8.ToArray()); jp.SetNull("$.x.y[2]"u8); Console.WriteLine(jp); // [{"op":"replace","path":"/x/y/2","value":null}] Console.WriteLine(jp.ToString("J")); // {"x":{"y":[null,null,null]}}