Escaping support for JSON pointers

This commit is contained in:
Cyprien Autexier 2016-10-08 04:43:22 -07:00 committed by Kiran Challa
parent 373097e0dd
commit fe2453b93a
5 changed files with 158 additions and 1 deletions

View File

@ -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();
}
}
}

View File

@ -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);
}
}
}

View File

@ -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; }
}
}

View File

@ -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()
{

View File

@ -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);
});
}
}
}