Reputation: 4660
I am trying to make a class (that contains NodaTime
's ZonedDateTime
properties) to be deserialized correctly (using JSON.NET) but it doesn't seem to work.
I am referencing and use NodaTime.Serialization.JsonNet
as well.
The serialization goes well and the resulting JSON is correct, but the deserialization generates wrong ZonedDateTime
values.
Before using NodaTime.Serialization.JsonNet
I had written my own custom serializer for JSON.NET and I had the same problem. What I've noticed, is that the ReadJson()
method of my custom JsonConverter
was producing the correct deserialized ZonedDateTime
value, but when the constructor of the class hosting the ZonedDateTime
properties was called, the input values for the ZonedDateTime
properties were wrong.
Here is the code:
class Program
{
static void Main(string[] args)
{
var obj = new ZonedTimeDetails(ZonedDateTime.FromDateTimeOffset(DateTime.Now), ZonedDateTime.FromDateTimeOffset(DateTime.Now.AddHours(1)), false);
var json = JsonConvert.SerializeObject(obj, new FullJsonSerializerSettings());
var obj2 = JsonConvert.DeserializeObject<ZonedTimeDetails>(json, new FullJsonSerializerSettings());
return;
}
}
public class FullJsonSerializerSettings : JsonSerializerSettings
{
public FullJsonSerializerSettings()
{
ContractResolver = new AcTypeContractResolver((MemberInfo memberInfo) => {
if (memberInfo is PropertyInfo pi)
{
var methodInfo = pi.GetSetMethod(true);
if (methodInfo == null)
{
return o => false;
}
}
return o => true;
});
TypeNameHandling = TypeNameHandling.All;
TypeNameAssemblyFormat = FormatterAssemblyStyle.Full;
Converters.Add(NodaConverters.CreateZonedDateTimeConverter(DateTimeZoneProviders.Serialization));
}
}
public class AcTypeContractResolver : DefaultContractResolver
{
private readonly Predicate<object> _predicate;
private readonly Func<MemberInfo, Predicate<object>> _predicateFactory;
public AcTypeContractResolver(Predicate<object> predicate)
{
_predicate = predicate;
}
public AcTypeContractResolver(Func<MemberInfo, Predicate<object>> predicateFactory)
{
_predicateFactory = predicateFactory;
}
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
var property = base.CreateProperty(member, memberSerialization);
property.Ignored = false;
property.ShouldSerialize = _predicate ?? _predicateFactory?.Invoke(member);
property.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
return property;
}
}
public class ZonedTimeDetails
{
[JsonConstructor]
public ZonedTimeDetails(ZonedDateTime zoneStart, ZonedDateTime zoneEnd, bool isOverflow = false)
{
ZonedStart = zoneStart;
ZonedEnd = zoneEnd;
IsOverflow = isOverflow;
}
public ZonedDateTime ZonedStart { get; private set; }
public ZonedDateTime ZonedEnd { get; private set; }
public bool IsOverflow { get; private set; }
public DateTime Start => ZonedStart.ToDateTimeUnspecified();
public DateTime End => ZonedEnd.ToDateTimeUnspecified();
public double DurationMin => (ZonedEnd - ZonedStart).TotalMinutes;
}
And here is the entire project, if that helps you: https://mega.nz/#!hFc0RAbS!teJ3Y4JHqCx1aHxUVU4kUFs30xwTTyF6QTpRB0D1Fnw
If someone has any idea what is going wrong, let me know.
From what I can tell, the problem is in the ZonedTimeDetails
class, but I believe that this ought to be working. The property names of that class match the ctor argument names, so I cannot understand why I get wrong values in there during deserialization.
UPDATE:
If I make the setters for the ZonedDateTime
properties public, it works, but I need that class to be immutable. And according to other answers on SO (1, 2), this constructor injection should work.
In addition to that, before I use ZonedDateTime
, I was using DateTime
properties in that class. And things were working without a problem with the setters private.
Upvotes: 1
Views: 283
Reputation: 129777
The problem is that the parameter names in the ZonedTimeDetails
constructor do not match up with the JSON, which you are creating from serializing that same class. The property names have a d
(e.g. ZonedStart
) whereas the constructor property names do not (zoneStart
). So when the constructor is called, empty structs are being passed into those parameters.
To fix, just change your constructor parameter names to match the property names:
[JsonConstructor]
public ZonedTimeDetails(ZonedDateTime zonedStart, ZonedDateTime zonedEnd, bool isOverflow = false)
{
ZonedStart = zonedStart;
ZonedEnd = zonedEnd;
IsOverflow = isOverflow;
}
Upvotes: 3