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.
|
||||
// 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.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace Microsoft.AspNetCore.JsonPatch.Internal
|
||||
{
|
||||
|
|
@ -19,7 +21,7 @@ namespace Microsoft.AspNetCore.JsonPatch.Internal
|
|||
throw new ArgumentNullException(nameof(path));
|
||||
}
|
||||
|
||||
_segments = path.Split(new char[] { '/' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
_segments = ParsePath(path);
|
||||
}
|
||||
|
||||
public string LastSegment
|
||||
|
|
@ -36,5 +38,55 @@ namespace Microsoft.AspNetCore.JsonPatch.Internal
|
|||
}
|
||||
|
||||
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;
|
||||
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);
|
||||
}
|
||||
|
||||
[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]
|
||||
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