diff --git a/.gitignore b/.gitignore index ef145a3..84bd272 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,8 @@ *.suo *.user +# VSCode +/.ionide # Build results [Dd]ebug/ diff --git a/src/Morcatko.AspNetCore.JsonMergePatch/Builders/PatchBuilder.cs b/src/Morcatko.AspNetCore.JsonMergePatch/Builders/PatchBuilder.cs index 3476e0e..9c134d2 100644 --- a/src/Morcatko.AspNetCore.JsonMergePatch/Builders/PatchBuilder.cs +++ b/src/Morcatko.AspNetCore.JsonMergePatch/Builders/PatchBuilder.cs @@ -10,7 +10,7 @@ namespace Morcatko.AspNetCore.JsonMergePatch.Builder public class PatchBuilder where TModel : class { public JsonMergePatchDocument Build(TModel original, TModel patched) => PatchBuilder.Build(original, patched); - public JsonMergePatchDocument Build(string jsonObjectPatch) => PatchBuilder.Build(jsonObjectPatch); + public JsonMergePatchDocument Build(string jsonObjectPatch, JsonSerializerSettings serializerSettings = null) => PatchBuilder.Build(jsonObjectPatch, serializerSettings); public JsonMergePatchDocument Build(object jsonObjectPatch) => PatchBuilder.Build(jsonObjectPatch); public JsonMergePatchDocument Build(JObject jsonObjectPatch) => PatchBuilder.Build(jsonObjectPatch); } @@ -23,8 +23,8 @@ public static class PatchBuilder public static JsonMergePatchDocument Build(TModel original, TModel patched, JsonMergePatchOptions options = null) where TModel : class => Build(DiffBuilder.Build(original, patched) ?? new JObject(), options); - public static JsonMergePatchDocument Build(string jsonObjectPatch, JsonMergePatchOptions options = null) where TModel : class - => Build(JObject.Parse(jsonObjectPatch), options); + public static JsonMergePatchDocument Build(string jsonObjectPatch, JsonSerializerSettings serializerSettings, JsonMergePatchOptions options = null) where TModel : class + => Build(JsonConvert.DeserializeObject(jsonObjectPatch, serializerSettings), options); public static JsonMergePatchDocument Build(object jsonObjectPatch, JsonMergePatchOptions options = null) where TModel : class => Build(JObject.FromObject(jsonObjectPatch), options); diff --git a/src/Morcatko.AspNetCore.JsonMergePatch/Formatters/JsonMergePatchInputFormatter.cs b/src/Morcatko.AspNetCore.JsonMergePatch/Formatters/JsonMergePatchInputFormatter.cs index 874d1c9..961c179 100644 --- a/src/Morcatko.AspNetCore.JsonMergePatch/Formatters/JsonMergePatchInputFormatter.cs +++ b/src/Morcatko.AspNetCore.JsonMergePatch/Formatters/JsonMergePatchInputFormatter.cs @@ -73,7 +73,7 @@ public async override Task ReadRequestBodyAsync(InputForma var jsonSerializer = CreateJsonSerializer(); try { - var jToken = await JToken.LoadAsync(jsonReader); + var jToken = jsonSerializer.Deserialize(jsonReader); switch (jToken) { diff --git a/src/Morcatko.AspNetCore.JsonMergePatch/JsonMergePatchDocumentOfT.cs b/src/Morcatko.AspNetCore.JsonMergePatch/JsonMergePatchDocumentOfT.cs index 3969b30..3c7800e 100644 --- a/src/Morcatko.AspNetCore.JsonMergePatch/JsonMergePatchDocumentOfT.cs +++ b/src/Morcatko.AspNetCore.JsonMergePatch/JsonMergePatchDocumentOfT.cs @@ -1,6 +1,7 @@ using Microsoft.AspNetCore.JsonPatch; using Microsoft.AspNetCore.JsonPatch.Operations; using Morcatko.AspNetCore.JsonMergePatch.Builder; +using Newtonsoft.Json; using Newtonsoft.Json.Linq; using Newtonsoft.Json.Serialization; using System; @@ -29,8 +30,8 @@ public static JsonMergePatchDocument Build(TModel original, TMod public static JsonMergePatchDocument Build(JObject jsonObject) where TModel : class => new PatchBuilder().Build(jsonObject); - public static JsonMergePatchDocument Build(string jsonObject) where TModel : class - => new PatchBuilder().Build(jsonObject); + public static JsonMergePatchDocument Build(string jsonObject, JsonSerializerSettings serializerSettings = null) where TModel : class + => new PatchBuilder().Build(jsonObject, serializerSettings); } public class JsonMergePatchDocument : JsonMergePatchDocument where TModel : class diff --git a/test/Morcatko.AspNetCore.JsonMergePatch.Tests/Builder/Json/Simple.cs b/test/Morcatko.AspNetCore.JsonMergePatch.Tests/Builder/Json/Simple.cs index a5a6413..e2290a1 100644 --- a/test/Morcatko.AspNetCore.JsonMergePatch.Tests/Builder/Json/Simple.cs +++ b/test/Morcatko.AspNetCore.JsonMergePatch.Tests/Builder/Json/Simple.cs @@ -1,6 +1,7 @@ using Morcatko.AspNetCore.JsonMergePatch.Builder; using Newtonsoft.Json; using Newtonsoft.Json.Linq; +using System; using Xunit; namespace Morcatko.AspNetCore.JsonMergePatch.Tests.Builder.Json @@ -12,7 +13,8 @@ class SimpleClass [JsonProperty("Number")] public int Integer { get; set; } = 1; public string String { get; set; } = "abc"; - } + public DateTimeOffset Date { get; set; } = new DateTimeOffset(2019, 10, 29, 9, 38, 0, 0, TimeSpan.FromHours(2)); + } private readonly PatchBuilder builder = new PatchBuilder(); @@ -27,7 +29,22 @@ public void String() Assert.Equal(3, result.Integer); } - [Fact] + [Fact] + public void StringForDateTimeOffset() + { + var original = new SimpleClass(); + + var patch = builder.Build("{ date: \"2019-10-15T09:38:00+03:00\" }", new JsonSerializerSettings() + { + DateParseHandling = DateParseHandling.DateTimeOffset + }); + var result = patch.ApplyTo(original); + var expectedDate = new DateTimeOffset(2019, 10, 15, 9, 38, 0, 0, TimeSpan.FromHours(3)); + Assert.Equal(expectedDate, result.Date); + Assert.Equal(expectedDate.Offset, result.Date.Offset); + } + + [Fact] public void JObject() { var original = new SimpleClass(); diff --git a/test/Morcatko.AspNetCore.JsonMergePatch.Tests/Integration/MvcCoreTest.cs b/test/Morcatko.AspNetCore.JsonMergePatch.Tests/Integration/MvcCoreTest.cs index f02aa18..3bb1da6 100644 --- a/test/Morcatko.AspNetCore.JsonMergePatch.Tests/Integration/MvcCoreTest.cs +++ b/test/Morcatko.AspNetCore.JsonMergePatch.Tests/Integration/MvcCoreTest.cs @@ -1,3 +1,4 @@ +using System; using System.Threading.Tasks; using Xunit; @@ -54,5 +55,38 @@ await server.MergePatchAsync("api/data", new[] Assert.Equal(expected, patchedModel); } } - } + + [Fact] + public async Task PatchDateTimeOffset() + { + using (var server = Helper.CreateMvcServer()) + { + await server.PostAsync("api/data/0", GetTestModel()); + await server.PostAsync("api/data/1", GetTestModel()); + await server.PostAsync("api/data/2", GetTestModel()); + + var dateTime = new DateTimeOffset(2019, 10, 29, 9, 38, 0, 0, TimeSpan.FromHours(2)); + + await server.MergePatchAsync("api/data", new[] + { + new { id = 1, date = dateTime }, + new { id = 2, date = dateTime.AddDays(15) } + }); + + var patchedModel = await server.GetAsync("api/data/0"); + var expected = GetTestModel(); + Assert.Equal(expected, patchedModel); + + patchedModel = await server.GetAsync("api/data/1"); + expected = GetTestModel(); + expected.Date = dateTime; + Assert.Equal(expected, patchedModel); + + patchedModel = await server.GetAsync("api/data/2"); + expected = GetTestModel(); + expected.Date = dateTime.AddDays(15); + Assert.Equal(expected, patchedModel); + } + } + } } diff --git a/test/Morcatko.AspNetCore.JsonMergePatch.Tests/Integration/Server/Startups.cs b/test/Morcatko.AspNetCore.JsonMergePatch.Tests/Integration/Server/Startups.cs index 43b188b..9caaf3a 100644 --- a/test/Morcatko.AspNetCore.JsonMergePatch.Tests/Integration/Server/Startups.cs +++ b/test/Morcatko.AspNetCore.JsonMergePatch.Tests/Integration/Server/Startups.cs @@ -10,7 +10,12 @@ public void ConfigureServices(IServiceCollection services) { services .AddMvc() - .AddJsonMergePatch(); + .AddJsonOptions(settings => { + settings.SerializerSettings.DateFormatHandling = Newtonsoft.Json.DateFormatHandling.IsoDateFormat; + settings.SerializerSettings.DateParseHandling = Newtonsoft.Json.DateParseHandling.DateTimeOffset; + settings.SerializerSettings.DateTimeZoneHandling = Newtonsoft.Json.DateTimeZoneHandling.Utc; + }) + .AddJsonMergePatch(); services.AddSingleton(); } @@ -26,7 +31,8 @@ public void ConfigureServices(IServiceCollection services) { services .AddMvcCore() - .AddJsonMergePatch(); + + .AddJsonMergePatch(); services.AddSingleton(); } diff --git a/test/Morcatko.AspNetCore.JsonMergePatch.Tests/TestModel.cs b/test/Morcatko.AspNetCore.JsonMergePatch.Tests/TestModel.cs index 32d174a..41f3340 100644 --- a/test/Morcatko.AspNetCore.JsonMergePatch.Tests/TestModel.cs +++ b/test/Morcatko.AspNetCore.JsonMergePatch.Tests/TestModel.cs @@ -8,108 +8,112 @@ namespace Morcatko.AspNetCore.JsonMergePatch.Tests { - public class TestModel : IEquatable - { - public int Id { get; set; } - public int Integer { get; set; } - [Required] - public string String { get; set; } - public float Float { get; set; } - public bool Boolean { get; set; } - - [JsonProperty("NewName")] - public string Renamed { get; set; } - public SubModel SubModel { get; set; } - - public SimpleEnum SimpleEnum { get; set; } - [JsonConverter(typeof(StringEnumConverter))] - public ValueEnum ValueEnum { get; set; } - - public bool Equals(TestModel other) - { - //We are not comparing Id - return this.Integer == other.Integer - && this.String == other.String - && this.Float == other.Float - && this.Boolean == other.Boolean - && this.Renamed == other.Renamed - && this.SimpleEnum == other.SimpleEnum - && this.ValueEnum == other.ValueEnum - && Enumerable.SequenceEqual(this.SubModels?.Keys, other.SubModels?.Keys) - && Enumerable.SequenceEqual(this.SubModels?.Values, other.SubModels?.Values) - && ((this.SubModel == other.SubModel) - || this.SubModel.Equals(other.SubModel)); - } - - public Dictionary SubModels { get; set; } = new Dictionary(); - } - - public class SubModel : IEquatable - { - public string Value1 { get; set; } - public string Value2 { get; set; } - public int[] Numbers { get; set; } - - public SubSubModel SubSubModel { get; set; } - - public bool Equals(SubModel other) - { - return this.Value1 == other.Value1 - && this.Value2 == other.Value2 - && this.SubSubModel == other.SubSubModel - && ((this.Numbers == other.Numbers) - || Enumerable.SequenceEqual(this.Numbers, other.Numbers)); - } - } - - public class SubSubModel : IEquatable - { - public string Value1 { get; set; } - - public override bool Equals(object obj) - { - var model = obj as SubSubModel; - return model != null && - Value1 == model.Value1; - } - - public override int GetHashCode() - { - return -1092109975 + EqualityComparer.Default.GetHashCode(Value1); - } - - public static bool operator ==(SubSubModel model1, SubSubModel model2) - { - return EqualityComparer.Default.Equals(model1, model2); - } - - public static bool operator !=(SubSubModel model1, SubSubModel model2) - { - return !(model1 == model2); - } - - public bool Equals(SubSubModel other) - { - return this.Value1 == other.Value1; - } - - - } - - public enum SimpleEnum - { - zero = 0, - one = 1, - two = 2 - } - - public enum ValueEnum - { - [EnumMember(Value = "Meter")] - m, - [EnumMember(Value = "Feet")] - ft, - [EnumMember(Value = "Inch")] - i - } + public class TestModel : IEquatable + { + public int Id { get; set; } + public int Integer { get; set; } + [Required] + public string String { get; set; } + public float Float { get; set; } + public bool Boolean { get; set; } + + [JsonProperty("NewName")] + public string Renamed { get; set; } + public SubModel SubModel { get; set; } + + public SimpleEnum SimpleEnum { get; set; } + [JsonConverter(typeof(StringEnumConverter))] + public ValueEnum ValueEnum { get; set; } + + public DateTimeOffset? Date { get; set; } + + public bool Equals(TestModel other) + { + //We are not comparing Id + return this.Integer == other.Integer + && this.String == other.String + && this.Float == other.Float + && this.Boolean == other.Boolean + && this.Renamed == other.Renamed + && this.SimpleEnum == other.SimpleEnum + && this.ValueEnum == other.ValueEnum + && this.Date == other.Date + && this.Date.GetValueOrDefault().Offset == other.Date.GetValueOrDefault().Offset + && Enumerable.SequenceEqual(this.SubModels?.Keys, other.SubModels?.Keys) + && Enumerable.SequenceEqual(this.SubModels?.Values, other.SubModels?.Values) + && ((this.SubModel == other.SubModel) + || this.SubModel.Equals(other.SubModel)); + } + + public Dictionary SubModels { get; set; } = new Dictionary(); + } + + public class SubModel : IEquatable + { + public string Value1 { get; set; } + public string Value2 { get; set; } + public int[] Numbers { get; set; } + + public SubSubModel SubSubModel { get; set; } + + public bool Equals(SubModel other) + { + return this.Value1 == other.Value1 + && this.Value2 == other.Value2 + && this.SubSubModel == other.SubSubModel + && ((this.Numbers == other.Numbers) + || Enumerable.SequenceEqual(this.Numbers, other.Numbers)); + } + } + + public class SubSubModel : IEquatable + { + public string Value1 { get; set; } + + public override bool Equals(object obj) + { + var model = obj as SubSubModel; + return model != null && + Value1 == model.Value1; + } + + public override int GetHashCode() + { + return -1092109975 + EqualityComparer.Default.GetHashCode(Value1); + } + + public static bool operator ==(SubSubModel model1, SubSubModel model2) + { + return EqualityComparer.Default.Equals(model1, model2); + } + + public static bool operator !=(SubSubModel model1, SubSubModel model2) + { + return !(model1 == model2); + } + + public bool Equals(SubSubModel other) + { + return this.Value1 == other.Value1; + } + + + } + + public enum SimpleEnum + { + zero = 0, + one = 1, + two = 2 + } + + public enum ValueEnum + { + [EnumMember(Value = "Meter")] + m, + [EnumMember(Value = "Feet")] + ft, + [EnumMember(Value = "Inch")] + i + } }