Skip to content

Commit

Permalink
Fix a problem with offset lost during serialization as JObject.Parse …
Browse files Browse the repository at this point in the history
…or JToken.Parse are not using the serialization settings. (#25)
  • Loading branch information
bhugot authored and Morcatko committed Nov 1, 2019
1 parent f30e2bf commit bb0bbaa
Show file tree
Hide file tree
Showing 8 changed files with 179 additions and 115 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
*.suo
*.user

# VSCode
/.ionide

# Build results
[Dd]ebug/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ namespace Morcatko.AspNetCore.JsonMergePatch.Builder
public class PatchBuilder<TModel> where TModel : class
{
public JsonMergePatchDocument<TModel> Build(TModel original, TModel patched) => PatchBuilder.Build<TModel>(original, patched);
public JsonMergePatchDocument<TModel> Build(string jsonObjectPatch) => PatchBuilder.Build<TModel>(jsonObjectPatch);
public JsonMergePatchDocument<TModel> Build(string jsonObjectPatch, JsonSerializerSettings serializerSettings = null) => PatchBuilder.Build<TModel>(jsonObjectPatch, serializerSettings);
public JsonMergePatchDocument<TModel> Build(object jsonObjectPatch) => PatchBuilder.Build<TModel>(jsonObjectPatch);
public JsonMergePatchDocument<TModel> Build(JObject jsonObjectPatch) => PatchBuilder.Build<TModel>(jsonObjectPatch);
}
Expand All @@ -23,8 +23,8 @@ public static class PatchBuilder
public static JsonMergePatchDocument<TModel> Build<TModel>(TModel original, TModel patched, JsonMergePatchOptions options = null) where TModel : class
=> Build<TModel>(DiffBuilder.Build(original, patched) ?? new JObject(), options);

public static JsonMergePatchDocument<TModel> Build<TModel>(string jsonObjectPatch, JsonMergePatchOptions options = null) where TModel : class
=> Build<TModel>(JObject.Parse(jsonObjectPatch), options);
public static JsonMergePatchDocument<TModel> Build<TModel>(string jsonObjectPatch, JsonSerializerSettings serializerSettings, JsonMergePatchOptions options = null) where TModel : class
=> Build<TModel>(JsonConvert.DeserializeObject<JObject>(jsonObjectPatch, serializerSettings), options);

public static JsonMergePatchDocument<TModel> Build<TModel>(object jsonObjectPatch, JsonMergePatchOptions options = null) where TModel : class
=> Build<TModel>(JObject.FromObject(jsonObjectPatch), options);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ public async override Task<InputFormatterResult> ReadRequestBodyAsync(InputForma
var jsonSerializer = CreateJsonSerializer();
try
{
var jToken = await JToken.LoadAsync(jsonReader);
var jToken = jsonSerializer.Deserialize<JToken>(jsonReader);

switch (jToken)
{
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -29,8 +30,8 @@ public static JsonMergePatchDocument<TModel> Build<TModel>(TModel original, TMod
public static JsonMergePatchDocument<TModel> Build<TModel>(JObject jsonObject) where TModel : class
=> new PatchBuilder<TModel>().Build(jsonObject);

public static JsonMergePatchDocument<TModel> Build<TModel>(string jsonObject) where TModel : class
=> new PatchBuilder<TModel>().Build(jsonObject);
public static JsonMergePatchDocument<TModel> Build<TModel>(string jsonObject, JsonSerializerSettings serializerSettings = null) where TModel : class
=> new PatchBuilder<TModel>().Build(jsonObject, serializerSettings);
}

public class JsonMergePatchDocument<TModel> : JsonMergePatchDocument where TModel : class
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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<SimpleClass> builder = new PatchBuilder<SimpleClass>();

Expand All @@ -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();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System;
using System.Threading.Tasks;
using Xunit;

Expand Down Expand Up @@ -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<TestModel>("api/data/0");
var expected = GetTestModel();
Assert.Equal(expected, patchedModel);

patchedModel = await server.GetAsync<TestModel>("api/data/1");
expected = GetTestModel();
expected.Date = dateTime;
Assert.Equal(expected, patchedModel);

patchedModel = await server.GetAsync<TestModel>("api/data/2");
expected = GetTestModel();
expected.Date = dateTime.AddDays(15);
Assert.Equal(expected, patchedModel);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<IRepository, Repository>();
}

Expand All @@ -26,7 +31,8 @@ public void ConfigureServices(IServiceCollection services)
{
services
.AddMvcCore()
.AddJsonMergePatch();

.AddJsonMergePatch();
services.AddSingleton<IRepository, Repository>();
}

Expand Down
212 changes: 108 additions & 104 deletions test/Morcatko.AspNetCore.JsonMergePatch.Tests/TestModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,108 +8,112 @@

namespace Morcatko.AspNetCore.JsonMergePatch.Tests
{
public class TestModel : IEquatable<TestModel>
{
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<string, SubModel> SubModels { get; set; } = new Dictionary<string, SubModel>();
}

public class SubModel : IEquatable<SubModel>
{
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<SubSubModel>
{
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<string>.Default.GetHashCode(Value1);
}

public static bool operator ==(SubSubModel model1, SubSubModel model2)
{
return EqualityComparer<SubSubModel>.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<TestModel>
{
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<string, SubModel> SubModels { get; set; } = new Dictionary<string, SubModel>();
}

public class SubModel : IEquatable<SubModel>
{
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<SubSubModel>
{
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<string>.Default.GetHashCode(Value1);
}

public static bool operator ==(SubSubModel model1, SubSubModel model2)
{
return EqualityComparer<SubSubModel>.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
}
}

0 comments on commit bb0bbaa

Please sign in to comment.