JamesNK / Newtonsoft.Json

Json.NET is a popular high-performance JSON framework for .NET
https://www.newtonsoft.com/json
MIT License
10.74k stars 3.25k forks source link

Deserialization of TimeOnly fails for "HH:mm" formatted times #2810

Open dwfrancis opened 1 year ago

dwfrancis commented 1 year ago

Deserialization of TimeOnly fails for the HH:mm format - the seconds is required. This leads to compatibility issues since HH:mm is the default format returned by HTML <input type="time" /> The similar TimeSpan properly deserializes this format.

Source/destination types

TimeOnly

Source/destination JSON

"17:05"

Expected behavior

The time is deserialized as 17:05

Actual behavior

Error converting value "17:05" to type 'System.TimeOnly'.

Steps to reproduce

JsonConvert.DeserializeObject<TimeOnly>("\"17:05\"")
mariomeyrelles commented 1 year ago

I am seeing this issue when I try to retrieve "09:00" from Cosmos. Can this change be merged soon? The solution seems to reasonable.

stratdev3 commented 1 year ago

+1

michelebenolli commented 1 year ago

This is my temporary workaround while waiting for the issue to be fixed.

public class TimeOnlyConverter : JsonConverter
{
    public override object? ReadJson(JsonReader reader, Type type, object? existingValue, JsonSerializer serializer)
    {
        try
        {
            bool result = TimeOnly.TryParse(reader?.Value?.ToString(), out TimeOnly time);
            if (result)
            {
                return time;
            }
        }
        catch { }
        return Activator.CreateInstance(type);
    }

    public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer)
    {
        writer.WriteValue(((TimeOnly?)value)?.ToString("HH:mm", CultureInfo.InvariantCulture));
    }

    public override bool CanConvert(Type type)
    {
        return type == typeof(TimeOnly) || type == typeof(TimeOnly?);
    }
}
.AddNewtonsoftJson(o =>
{
    o.SerializerSettings.Converters.Add(new TimeOnlyConverter());
});
JCDriessen commented 9 months ago

To add to @michelebenolli workarround: In my case, it was a TimeOnly property in an object I wanted to deserialize. This is also possible, you just need to add a ContractRevolver to change the behavior of the serializer/deserializer to use the custom converter instead of the normal one:

internal sealed class TimeOnlyContractResolver : DefaultContractResolver
    {
        protected override JsonProperty CreateProperty( MemberInfo member, MemberSerialization memberSerialization )
        {
            var property = base.CreateProperty( member, memberSerialization );

            if( property.PropertyType == typeof( TimeOnly ) )
            {
                property.Converter = new TimeOnlyConverter();
            }

            return property;
        }
    }

Then use it like so:

YourObject Result = JsonConvert.DeserializeObject<YourObject>(yourJsonString, new JsonSerializerSettings {
    ContractResolver = new TimeOnlyContractResolver() 
});

It is a bit of a workaround so I hope that this change will be merged after all this time. However, it does make you learn about how serialization/deserialization works :).

Some sites I used for this information: https://stackoverflow.com/questions/45643903/anyway-to-get-jsonconvert-serializeobject-to-ignore-the-jsonconverter-attribute https://github.com/JamesNK/Newtonsoft.Json/issues/2056