Skip to content

Added System.Text.Json serialization-based JsonPatch implementation #61313

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

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions AspNetCore.slnx
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,10 @@
<Project Path="src/DefaultBuilder/test/Microsoft.AspNetCore.Tests/Microsoft.AspNetCore.Tests.csproj" />
</Folder>
<Folder Name="/src/Features/" Id="e81caa0a-8d6a-e3b6-28b3-ab416e1657a7" />
<Folder Name="/src/Features/JsonPatch.SystemTextJson/" Id="02ea681e-c7d8-13c7-8484-4ac65e1b71e8">
<Project Path="src/Features/JsonPatch.SystemTextJson/src/Microsoft.AspNetCore.JsonPatch.SystemTextJson.csproj" />
<Project Path="src/Features/JsonPatch.SystemTextJson/test/Microsoft.AspNetCore.JsonPatch.SystemTextJson.Tests.csproj" />
</Folder>
<Folder Name="/src/Features/JsonPatch/" Id="077a40ee-d664-9e06-acde-21ca54b47638">
<Project Path="src/Features/JsonPatch/src/Microsoft.AspNetCore.JsonPatch.csproj" />
<Project Path="src/Features/JsonPatch/test/Microsoft.AspNetCore.JsonPatch.Tests.csproj" />
Expand Down
12 changes: 12 additions & 0 deletions src/Features/JsonPatch.SystemTextJson/.vsconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"version": "1.0",
"components": [
"Microsoft.Net.Component.4.6.2.TargetingPack",
"Microsoft.Net.Component.4.7.2.SDK",
"Microsoft.Net.Component.4.7.2.TargetingPack",
"Microsoft.VisualStudio.Workload.ManagedDesktop",
"Microsoft.VisualStudio.Workload.NetCoreTools",
"Microsoft.VisualStudio.Workload.NetWeb",
"Microsoft.VisualStudio.Workload.VisualStudioExtension"
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"solution": {
"path": "..\\..\\..\\AspNetCore.slnx",
"projects" : [
"src\\Features\\JsonPatch.SystemTextJson\\src\\Microsoft.AspNetCore.JsonPatch.SystemTextJson.csproj",
"src\\Features\\JsonPatch.SystemTextJson\\test\\Microsoft.AspNetCore.JsonPatch.SystemTextJson.Tests.csproj"
]
}
}
4 changes: 4 additions & 0 deletions src/Features/JsonPatch.SystemTextJson/build.cmd
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@

@ECHO OFF
SET RepoRoot=%~dp0..\..\..
%RepoRoot%\eng\build.cmd -projects %~dp0**\*.*proj %*
7 changes: 7 additions & 0 deletions src/Features/JsonPatch.SystemTextJson/build.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#!/usr/bin/env bash

set -euo pipefail

DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
repo_root="$DIR/../.."
"$repo_root/eng/build.sh" --projects "$DIR/**/*.*proj" "$@"
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Collections;
using System.Collections.Generic;
using System.Text.Json.Nodes;
using Microsoft.AspNetCore.JsonPatch.SystemTextJson.Internal;
using Microsoft.AspNetCore.Shared;

namespace Microsoft.AspNetCore.JsonPatch.SystemTextJson.Adapters;

/// <summary>
/// The default AdapterFactory to be used for resolving <see cref="IAdapter"/>.
/// </summary>
internal class AdapterFactory : IAdapterFactory
{
internal static AdapterFactory Default { get; } = new();

/// <inheritdoc />
public virtual IAdapter Create(object target)
{
ArgumentNullThrowHelper.ThrowIfNull(target);

var typeToConvert = target.GetType();
if (typeToConvert.IsGenericType && typeToConvert.GetGenericTypeDefinition() == typeof(Dictionary<,>))
{
return (IAdapter)Activator.CreateInstance(typeof(DictionaryAdapter<,>).MakeGenericType(typeToConvert.GenericTypeArguments[0], typeToConvert.GenericTypeArguments[1]));
}

return target switch
{
JsonObject => new JsonObjectAdapter(),
JsonArray => new ListAdapter(),
IList => new ListAdapter(),
_ => new PocoAdapter()
};
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using Microsoft.AspNetCore.JsonPatch.SystemTextJson.Internal;

namespace Microsoft.AspNetCore.JsonPatch.SystemTextJson.Adapters;

/// <summary>
/// Defines the operations used for loading an <see cref="IAdapter"/> based on the current object and ContractResolver.
/// </summary>
internal interface IAdapterFactory
{
/// <summary>
/// Creates an <see cref="IAdapter"/> for the current object
/// </summary>
/// <param name="target">The target object</param>
/// <returns>The needed <see cref="IAdapter"/></returns>
IAdapter Create(object target);
}
111 changes: 111 additions & 0 deletions src/Features/JsonPatch.SystemTextJson/src/Adapters/IObjectAdapter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using Microsoft.AspNetCore.JsonPatch.SystemTextJson.Operations;

namespace Microsoft.AspNetCore.JsonPatch.SystemTextJson.Adapters;

/// <summary>
/// Defines the operations that can be performed on a JSON patch document.
/// </summary>
public interface IObjectAdapter
{
/// <summary>
/// Using the "add" operation a new value is inserted into the root of the target
/// document, into the target array at the specified valid index, or to a target object at
/// the specified location.
///
/// When adding to arrays, the specified index MUST NOT be greater than the number of elements in the array.
/// To append the value to the array, the index of "-" character is used (see [RFC6901]).
///
/// When adding to an object, if an object member does not already exist, a new member is added to the object at the
/// specified location or if an object member does exist, that member's value is replaced.
///
/// The operation object MUST contain a "value" member whose content
/// specifies the value to be added.
///
/// For example:
///
/// { "op": "add", "path": "/a/b/c", "value": [ "foo", "bar" ] }
///
/// See RFC 6902 <see href="https://tools.ietf.org/html/rfc6902#page-4"/>
/// </summary>
/// <param name="operation">The add operation.</param>
/// <param name="objectToApplyTo">Object to apply the operation to.</param>
void Add(Operation operation, object objectToApplyTo);

/// <summary>
/// Using the "copy" operation, a value is copied from a specified location to the
/// target location.
///
/// The operation object MUST contain a "from" member, which references the location in the
/// target document to copy the value from.
///
/// The "from" location MUST exist for the operation to be successful.
///
/// For example:
///
/// { "op": "copy", "from": "/a/b/c", "path": "/a/b/e" }
///
/// See RFC 6902 <see href="https://tools.ietf.org/html/rfc6902#page-7"/>
/// </summary>
/// <param name="operation">The copy operation.</param>
/// <param name="objectToApplyTo">Object to apply the operation to.</param>
void Copy(Operation operation, object objectToApplyTo);

/// <summary>
/// Using the "move" operation the value at a specified location is removed and
/// added to the target location.
///
/// The operation object MUST contain a "from" member, which references the location in the
/// target document to move the value from.
///
/// The "from" location MUST exist for the operation to be successful.
///
/// For example:
///
/// { "op": "move", "from": "/a/b/c", "path": "/a/b/d" }
///
/// A location cannot be moved into one of its children.
///
/// See RFC 6902 <see href="https://tools.ietf.org/html/rfc6902#page-6"/>
/// </summary>
/// <param name="operation">The move operation.</param>
/// <param name="objectToApplyTo">Object to apply the operation to.</param>
void Move(Operation operation, object objectToApplyTo);

/// <summary>
/// Using the "remove" operation the value at the target location is removed.
///
/// The target location MUST exist for the operation to be successful.
///
/// For example:
///
/// { "op": "remove", "path": "/a/b/c" }
///
/// If removing an element from an array, any elements above the
/// specified index are shifted one position to the left.
///
/// See RFC 6902 <see href="https://tools.ietf.org/html/rfc6902#page-6"/>
/// </summary>
/// <param name="operation">The remove operation.</param>
/// <param name="objectToApplyTo">Object to apply the operation to.</param>
void Remove(Operation operation, object objectToApplyTo);

/// <summary>
/// Using the "replace" operation the value at the target location is replaced
/// with a new value. The operation object MUST contain a "value" member
/// which specifies the replacement value.
///
/// The target location MUST exist for the operation to be successful.
///
/// For example:
///
/// { "op": "replace", "path": "/a/b/c", "value": 42 }
///
/// See RFC 6902 <see href="https://tools.ietf.org/html/rfc6902#page-6"/>
/// </summary>
/// <param name="operation">The replace operation.</param>
/// <param name="objectToApplyTo">Object to apply the operation to.</param>
void Replace(Operation operation, object objectToApplyTo);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using Microsoft.AspNetCore.JsonPatch.SystemTextJson.Operations;

namespace Microsoft.AspNetCore.JsonPatch.SystemTextJson.Adapters;

/// <summary>
/// Defines the operations that can be performed on a JSON patch document, including "test".
/// </summary>
public interface IObjectAdapterWithTest : IObjectAdapter
{
/// <summary>
/// Using the "test" operation a value at the target location is compared for
/// equality to a specified value.
///
/// The operation object MUST contain a "value" member that specifies
/// value to be compared to the target location's value.
///
/// The target location MUST be equal to the "value" value for the
/// operation to be considered successful.
///
/// For example:
/// { "op": "test", "path": "/a/b/c", "value": "foo" }
///
/// See RFC 6902 <see href="https://tools.ietf.org/html/rfc6902#page-7"/>
/// </summary>
/// <param name="operation">The test operation.</param>
/// <param name="objectToApplyTo">Object to apply the operation to.</param>
void Test(Operation operation, object objectToApplyTo);
}
Loading
Loading