Escaping support for JSON pointers
This commit is contained in:
parent
373097e0dd
commit
fe2453b93a
|
|
@ -1,8 +1,10 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using Microsoft.AspNetCore.JsonPatch.Exceptions;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.JsonPatch.Internal
|
namespace Microsoft.AspNetCore.JsonPatch.Internal
|
||||||
{
|
{
|
||||||
|
|
@ -19,7 +21,7 @@ namespace Microsoft.AspNetCore.JsonPatch.Internal
|
||||||
throw new ArgumentNullException(nameof(path));
|
throw new ArgumentNullException(nameof(path));
|
||||||
}
|
}
|
||||||
|
|
||||||
_segments = path.Split(new char[] { '/' }, StringSplitOptions.RemoveEmptyEntries);
|
_segments = ParsePath(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
public string LastSegment
|
public string LastSegment
|
||||||
|
|
@ -36,5 +38,55 @@ namespace Microsoft.AspNetCore.JsonPatch.Internal
|
||||||
}
|
}
|
||||||
|
|
||||||
public IReadOnlyList<string> Segments => _segments ?? Empty;
|
public IReadOnlyList<string> Segments => _segments ?? Empty;
|
||||||
|
|
||||||
|
private static string[] ParsePath(string path)
|
||||||
|
{
|
||||||
|
var strings = new List<string>();
|
||||||
|
var sb = new StringBuilder(path.Length);
|
||||||
|
|
||||||
|
for (var i = 0; i < path.Length; i++)
|
||||||
|
{
|
||||||
|
if (path[i] == '/')
|
||||||
|
{
|
||||||
|
if (sb.Length > 0)
|
||||||
|
{
|
||||||
|
strings.Add(sb.ToString());
|
||||||
|
sb.Length = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (path[i] == '~')
|
||||||
|
{
|
||||||
|
++i;
|
||||||
|
if (i >= path.Length)
|
||||||
|
{
|
||||||
|
throw new JsonPatchException(Resources.FormatInvalidValueForPath(path), null);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (path[i] == '0')
|
||||||
|
{
|
||||||
|
sb.Append('~');
|
||||||
|
}
|
||||||
|
else if (path[i] == '1')
|
||||||
|
{
|
||||||
|
sb.Append('/');
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new JsonPatchException(Resources.FormatInvalidValueForPath(path), null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
sb.Append(path[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sb.Length > 0)
|
||||||
|
{
|
||||||
|
strings.Add(sb.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
return strings.ToArray();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -144,5 +144,28 @@ namespace Microsoft.AspNetCore.JsonPatch
|
||||||
var pathToCheck = deserialized.Operations.First().path;
|
var pathToCheck = deserialized.Operations.First().path;
|
||||||
Assert.Equal(pathToCheck, "/anothername");
|
Assert.Equal(pathToCheck, "/anothername");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Add_OnApplyFromJson_EscapingHandledOnComplexJsonPropertyNameOnJsonDocument()
|
||||||
|
{
|
||||||
|
var doc = new JsonPropertyComplexNameDTO()
|
||||||
|
{
|
||||||
|
FooSlashBars = "InitialName",
|
||||||
|
FooSlashTilde = new SimpleDTO
|
||||||
|
{
|
||||||
|
StringProperty = "Initial Value"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// serialization should serialize to "AnotherName"
|
||||||
|
var serialized = "[{\"value\":\"Kevin\",\"path\":\"/foo~1bar~0\",\"op\":\"add\"},{\"value\":\"Final Value\",\"path\":\"/foo~1~0/StringProperty\",\"op\":\"replace\"}]";
|
||||||
|
var deserialized =
|
||||||
|
JsonConvert.DeserializeObject<JsonPatchDocument<JsonPropertyComplexNameDTO>>(serialized);
|
||||||
|
|
||||||
|
deserialized.ApplyTo(doc);
|
||||||
|
|
||||||
|
Assert.Equal("Kevin", doc.FooSlashBars);
|
||||||
|
Assert.Equal("Final Value", doc.FooSlashTilde.StringProperty);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.JsonPatch
|
||||||
|
{
|
||||||
|
public class JsonPropertyComplexNameDTO
|
||||||
|
{
|
||||||
|
[JsonProperty("foo/bar~")]
|
||||||
|
public string FooSlashBars { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("foo/~")]
|
||||||
|
public SimpleDTO FooSlashTilde { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1821,6 +1821,33 @@ namespace Microsoft.AspNetCore.JsonPatch.Adapters
|
||||||
Assert.Equal("James", actualValue1.Name);
|
Assert.Equal("James", actualValue1.Name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Replace_WhenDictionary_ValueAPocoType_WithEscaping_Succeeds()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var key1 = "Foo/Name";
|
||||||
|
var value1 = new Customer() { Name = "Jamesss" };
|
||||||
|
var key2 = "Foo";
|
||||||
|
var value2 = new Customer() { Name = "Mike" };
|
||||||
|
var model = new Class8();
|
||||||
|
model.DictionaryOfStringToCustomer[key1] = value1;
|
||||||
|
model.DictionaryOfStringToCustomer[key2] = value2;
|
||||||
|
var patchDocument = new JsonPatchDocument();
|
||||||
|
patchDocument.Replace($"/DictionaryOfStringToCustomer/Foo~1Name/Name", "James");
|
||||||
|
|
||||||
|
// Act
|
||||||
|
patchDocument.ApplyTo(model);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(2, model.DictionaryOfStringToCustomer.Count);
|
||||||
|
var actualValue1 = model.DictionaryOfStringToCustomer[key1];
|
||||||
|
var actualValue2 = model.DictionaryOfStringToCustomer[key2];
|
||||||
|
Assert.NotNull(actualValue1);
|
||||||
|
Assert.Equal("James", actualValue1.Name);
|
||||||
|
Assert.Equal("Mike", actualValue2.Name);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void Replace_DeepNested_DictionaryValue_Succeeds()
|
public void Replace_DeepNested_DictionaryValue_Succeeds()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,39 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using Microsoft.AspNetCore.JsonPatch.Exceptions;
|
||||||
|
using Microsoft.AspNetCore.JsonPatch.Internal;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.JsonPatch.Test
|
||||||
|
{
|
||||||
|
public class ParsedPathTests
|
||||||
|
{
|
||||||
|
[Theory]
|
||||||
|
[InlineData("foo/bar~0baz", new string[] { "foo", "bar~baz" })]
|
||||||
|
[InlineData("foo/bar~00baz", new string[] { "foo", "bar~0baz" })]
|
||||||
|
[InlineData("foo/bar~01baz", new string[] { "foo", "bar~1baz" })]
|
||||||
|
[InlineData("foo/bar~10baz", new string[] { "foo", "bar/0baz" })]
|
||||||
|
[InlineData("foo/bar~1baz", new string[] { "foo", "bar/baz" })]
|
||||||
|
[InlineData("foo/bar~0/~0/~1~1/~0~0/baz", new string[] { "foo", "bar~", "~", "//", "~~", "baz" })]
|
||||||
|
[InlineData("~0~1foo", new string[] { "~/foo" })]
|
||||||
|
public void ParsingValidPathShouldSucceed(string path, string[] expected)
|
||||||
|
{
|
||||||
|
var parsedPath = new ParsedPath(path);
|
||||||
|
Assert.Equal(expected, parsedPath.Segments);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("foo/bar~")]
|
||||||
|
[InlineData("~")]
|
||||||
|
[InlineData("~2")]
|
||||||
|
[InlineData("foo~3bar")]
|
||||||
|
public void PathWithInvalidEscapeSequenceShouldFail(string path)
|
||||||
|
{
|
||||||
|
Assert.Throws<JsonPatchException>(() =>
|
||||||
|
{
|
||||||
|
var parsedPath = new ParsedPath(path);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue