// 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 System.Collections.Generic; using System.Linq; using System.Reflection; using Newtonsoft.Json; using Newtonsoft.Json.Serialization; using Xunit; namespace Microsoft.AspNetCore.JsonPatch { public class JsonPatchDocumentJsonPropertyAttributeTest { [Fact] public void Add_ToRoot_OfListOfObjects_AtEndOfList() { var patchDoc = new JsonPatchDocument>(); patchDoc.Add(p => p, new JsonPropertyObject()); var serialized = JsonConvert.SerializeObject(patchDoc); var deserialized = JsonConvert.DeserializeObject>(serialized); // get path var pathToCheck = deserialized.Operations.First().path; Assert.Equal("/-", pathToCheck); } [Fact] public void Add_ToRoot_OfListOfObjects_AtGivenPosition() { var patchDoc = new JsonPatchDocument>(); patchDoc.Add(p => p[3], new JsonPropertyObject()); var serialized = JsonConvert.SerializeObject(patchDoc); var deserialized = JsonConvert.DeserializeObject>(serialized); // get path var pathToCheck = deserialized.Operations.First().path; Assert.Equal("/3", pathToCheck); } [Fact] public void Add_WithExpression_RespectsJsonPropertyName_ForModelProperty() { var patchDoc = new JsonPatchDocument(); patchDoc.Add(p => p.Name, "John"); var serialized = JsonConvert.SerializeObject(patchDoc); // serialized value should have "AnotherName" as path // deserialize to a JsonPatchDocument to check var deserialized = JsonConvert.DeserializeObject>(serialized); // get path var pathToCheck = deserialized.Operations.First().path; Assert.Equal("/AnotherName", pathToCheck); } [Fact] public void Add_WithExpressionOnStringProperty_FallsbackToPropertyName_WhenJsonPropertyName_IsEmpty() { // Arrange var patchDoc = new JsonPatchDocument(); patchDoc.Add(m => m.StringProperty, "Test"); var serialized = JsonConvert.SerializeObject(patchDoc); // Act var deserialized = JsonConvert.DeserializeObject>(serialized); // Assert var pathToCheck = deserialized.Operations.First().path; Assert.Equal("/StringProperty", pathToCheck); } [Fact] public void Add_WithExpressionOnArrayProperty_FallsbackToPropertyName_WhenJsonPropertyName_IsEmpty() { // Arrange var patchDoc = new JsonPatchDocument(); patchDoc.Add(m => m.ArrayProperty, "James"); var serialized = JsonConvert.SerializeObject(patchDoc); // Act var deserialized = JsonConvert.DeserializeObject>(serialized); // Assert var pathToCheck = deserialized.Operations.First().path; Assert.Equal("/ArrayProperty/-", pathToCheck); } [Fact] public void Add_WithExpression_RespectsJsonPropertyName_WhenApplyingToDifferentlyTypedClassWithPropertyMatchingJsonPropertyName() { var patchDocToSerialize = new JsonPatchDocument(); patchDocToSerialize.Add(p => p.Name, "John"); // the patchdoc will deserialize to "anothername". We should thus be able to apply // it to a class that HAS that other property name. var doc = new JsonPropertyWithAnotherNameObject() { AnotherName = "InitialValue" }; var serialized = JsonConvert.SerializeObject(patchDocToSerialize); var deserialized = JsonConvert.DeserializeObject> (serialized); deserialized.ApplyTo(doc); Assert.Equal("John", doc.AnotherName); } [Fact] public void Add_WithExpression_RespectsJsonPropertyName_WhenApplyingToSameTypedClassWithMatchingJsonPropertyName() { var patchDocToSerialize = new JsonPatchDocument(); patchDocToSerialize.Add(p => p.Name, "John"); // the patchdoc will deserialize to "anothername". As JsonPropertyDTO has // a JsonProperty signifying that "Name" should be deseriallized from "AnotherName", // we should be able to apply the patchDoc. var doc = new JsonPropertyObject() { Name = "InitialValue" }; var serialized = JsonConvert.SerializeObject(patchDocToSerialize); var deserialized = JsonConvert.DeserializeObject> (serialized); deserialized.ApplyTo(doc); Assert.Equal("John", doc.Name); } [Fact] public void Add_OnApplyFromJson_RespectsJsonPropertyNameOnJsonDocument() { var doc = new JsonPropertyObject() { Name = "InitialValue" }; // serialization should serialize to "AnotherName" var serialized = "[{\"value\":\"Kevin\",\"path\":\"/AnotherName\",\"op\":\"add\"}]"; var deserialized = JsonConvert.DeserializeObject>(serialized); deserialized.ApplyTo(doc); Assert.Equal("Kevin", doc.Name); } [Fact] public void Remove_OnApplyFromJson_RespectsJsonPropertyNameOnJsonDocument() { var doc = new JsonPropertyObject() { Name = "InitialValue" }; // serialization should serialize to "AnotherName" var serialized = "[{\"path\":\"/AnotherName\",\"op\":\"remove\"}]"; var deserialized = JsonConvert.DeserializeObject>(serialized); deserialized.ApplyTo(doc); Assert.Null(doc.Name); } [Fact] public void Add_OnApplyFromJson_RespectsInheritedJsonPropertyNameOnJsonDocument() { var doc = new JsonPropertyWithInheritanceObject() { Name = "InitialName" }; // serialization should serialize to "AnotherName" var serialized = "[{\"value\":\"Kevin\",\"path\":\"/AnotherName\",\"op\":\"add\"}]"; var deserialized = JsonConvert.DeserializeObject>(serialized); deserialized.ApplyTo(doc); Assert.Equal("Kevin", doc.Name); } [Fact] public void Add_WithExpression_RespectsJsonPropertyName_ForInheritedModelProperty() { var patchDoc = new JsonPatchDocument(); patchDoc.Add(p => p.Name, "John"); var serialized = JsonConvert.SerializeObject(patchDoc); // serialized value should have "AnotherName" as path // deserialize to a JsonPatchDocument to check var deserialized = JsonConvert.DeserializeObject>(serialized); // get path var pathToCheck = deserialized.Operations.First().path; Assert.Equal("/AnotherName", pathToCheck); } [Fact] public void Add_OnApplyFromJson_EscapingHandledOnComplexJsonPropertyNameOnJsonDocument() { var doc = new JsonPropertyComplexNameObject() { FooSlashBars = "InitialName", FooSlashTilde = new SimpleObject { 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>(serialized); deserialized.ApplyTo(doc); Assert.Equal("Kevin", doc.FooSlashBars); Assert.Equal("Final Value", doc.FooSlashTilde.StringProperty); } [Fact] public void Move_WithExpression_FallsbackToPropertyName_WhenJsonPropertyName_IsEmpty() { // Arrange var patchDoc = new JsonPatchDocument(); patchDoc.Move(m => m.StringProperty, m => m.StringProperty2); var serialized = JsonConvert.SerializeObject(patchDoc); // Act var deserialized = JsonConvert.DeserializeObject>(serialized); // Assert var fromPath = deserialized.Operations.First().from; Assert.Equal("/StringProperty", fromPath); var toPath = deserialized.Operations.First().path; Assert.Equal("/StringProperty2", toPath); } [Fact] public void Add_WithExpression_AndCustomContractResolver_UsesPropertyName_SetByContractResolver() { // Arrange var patchDoc = new JsonPatchDocument(); patchDoc.ContractResolver = new CustomContractResolver(); patchDoc.Add(m => m.SSN, "123-45-6789"); var serialized = JsonConvert.SerializeObject(patchDoc); // Act var deserialized = JsonConvert.DeserializeObject>(serialized); // Assert var path = deserialized.Operations.First().path; Assert.Equal("/SocialSecurityNumber", path); } private class JsonPropertyWithNoPropertyName { [JsonProperty] public string StringProperty { get; set; } [JsonProperty] public string[] ArrayProperty { get; set; } [JsonProperty] public string StringProperty2 { get; set; } [JsonProperty] public string SSN { get; set; } } private class CustomContractResolver : DefaultContractResolver { protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization) { var jsonProperty = base.CreateProperty(member, memberSerialization); if (jsonProperty.PropertyName == "SSN") { jsonProperty.PropertyName = "SocialSecurityNumber"; } return jsonProperty; } } } }