nodatime / nodatime

A better date and time API for .NET

Home Page:https://nodatime.org

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Deserialization with Json.NET problem when using JToken.ToObject

omkelderman opened this issue · comments

I'll first start with admitting that I am not entirely sure this is even a problem with NodaTime, it could very well be a problem with Json.NET internals, but I'll elaborate on that later. Lets first start with a bit of sample code:

using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using NodaTime;
using NodaTime.Serialization.JsonNet;

const string thingStr = """
    {
      "Str": "some value",
      "Instant": "2023-09-25T14:51:42Z"
    }
    """;

var jsonOptions = new JsonSerializerSettings().ConfigureForNodaTime(DateTimeZoneProviders.Tzdb);

try
{
    var result = JsonConvert.DeserializeObject<ThingWithNodaInstant>(thingStr, jsonOptions);
    Console.WriteLine("with JsonConvert.DeserializeObject: {0}", result);
}
catch (Exception ex)
{
    Console.WriteLine("with JsonConvert.DeserializeObject: {0}", ex);
}

Console.WriteLine();

var jToken = JToken.Parse(thingStr);
var serializer = JsonSerializer.Create(jsonOptions);

try
{
    var result = jToken.ToObject<ThingWithNodaInstant>(serializer);
    Console.WriteLine("with JToken: {0}", result);
}
catch (Exception ex)
{
    Console.WriteLine("with JToken: {0}", ex);
}


public record ThingWithNodaInstant(string Str, Instant Instant);

this program gives the following output for me:

with JsonConvert.DeserializeObject: ThingWithNodaInstant { Str = some value, Instant = 2023-09-25T14:51:42Z }

with JToken: Newtonsoft.Json.JsonSerializationException: Cannot convert value to NodaTime.Instant
 ---> NodaTime.Utility.InvalidNodaDataException: Unexpected token parsing Instant. Expected String, got Date.
   at NodaTime.Serialization.JsonNet.NodaPatternConverter`1.ReadJsonImpl(JsonReader reader, JsonSerializer serializer)
   at NodaTime.Serialization.JsonNet.NodaConverterBase`1.ReadJson(JsonReader reader, Type objectType, Object existingValue, JsonSerializer serializer)
   --- End of inner exception stack trace ---
   at NodaTime.Serialization.JsonNet.NodaConverterBase`1.ReadJson(JsonReader reader, Type objectType, Object existingValue, JsonSerializer serializer)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.DeserializeConvertable(JsonConverter converter, JsonReader reader, Type objectType, Object existingValue)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.ResolvePropertyAndCreatorValues(JsonObjectContract contract, JsonProperty containerProperty, JsonReader reader, Type objectType)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateObjectUsingCreatorWithParameters(JsonReader reader, JsonObjectContract contract, JsonProperty containerProperty, ObjectConstructor`1 creator, String id)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateNewObject(JsonReader reader, JsonObjectContract objectContract, JsonProperty containerMember, JsonProperty containerProperty, String id, Boolean& createdFromNonDefaultCreator)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateObject(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerMember, Object existingValue)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateValueInternal(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerMember, Object existingValue)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.Deserialize(JsonReader reader, Type objectType, Boolean checkAdditionalContent)
   at Newtonsoft.Json.JsonSerializer.DeserializeInternal(JsonReader reader, Type objectType)
   at Newtonsoft.Json.JsonSerializer.Deserialize(JsonReader reader, Type objectType)
   at Newtonsoft.Json.Linq.JToken.ToObject(Type objectType, JsonSerializer jsonSerializer)
   at Newtonsoft.Json.Linq.JToken.ToObject[T](JsonSerializer jsonSerializer)
   at Program.<Main>$(String[] args) in C:\Dev\Kaas\NodaTimeJsonProblem\Program.cs:line 32

So basically normal JsonConvert.DeserializeObject works just fine, but when working with the JToken stuff, it doesnt work anymore.

The "Unexpected token parsing Instant. Expected String, got Date" makes me thing this is somehow a problem with the DateParseHandling stuff from Json.NET, but as far as I can see the ConfigureForNodaTime(...) sets that to None. I am not sure if there is something else going on here, or if this simply a bug in Json.NET where the DateParseHandling property from the settings is ignored with the jToken.ToObject<ThingWithNodaInstant>(serializer) call and I should file a bug report there?

As for why I am even using the whole JToken part and not just a normal DeserializeObject, I am not, this is a very simplified example. In the actual case I am using a third party library where the whole JToken magic happens in code I do not control, I simply provide the JsonSerializerSettings and some of the data types that go through that library have a NodaTime Instant in them, and thats when I ran into issues.

welp, looks like I typed too soon. Just now also found https://stackoverflow.com/questions/54907568/converting-jtoken-into-net-types-with-custom-serializersettings and understood the whole JToken magic a bit better, and looks like my problem actually sits in the JToken.Parse(thingStr) bit which actually doesnt get the custom JsonSerializerSettings so it doesnt know about the DateParseHandling stuff, in other words, this is definately not a nodatime serialization problem, but a me problem. Sorry about that 😂.

Looks like I'll have to ask that third party lib author I was talking about to not use JToken.Parse(thingStr) but JsonConvert.DeserializeObject<JToken>(jsonString, parseSettings).