简体   繁体   中英

How do I deserialise different NodaTime LocalDate patterns within the same JSON object

I'm trying to use NodaTime to interpret dates retrieved from a third-party API. The dates come in an annoying array of formats in the same response, one that I'm particularly having trouble with is similar to:

{
    "ShortDate": "2017-01-01",
    "LongDate": "01 January 2017"
}

I can correctly deserialise one format or the other using NodaPatternConverter, but not both.

A simple example showing the problem is like this:

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

namespace NodaLocalDateConverterTest
{
    class ExampleDatedModel
    {
        public LocalDate ShortDate { get; set; }

        public LocalDate LongDate { get; set; }
    }


    class Program
    {
        static void Main(string[] args)
        {
            var exampleJsonString =
@"{
    ""ShortDate"": ""2017-01-01"",
    ""LongDate"": ""01 January 2017""
}";

            var serialisationSettings = new JsonSerializerSettings();
            //NodaTime default converter supports ShortDate format
            serialisationSettings.ConfigureForNodaTime(DateTimeZoneProviders.Tzdb);

            //Exception on LongDate property
            var deserialisedExample1 = JsonConvert.DeserializeObject<ExampleDatedModel>(exampleJsonString, serialisationSettings);

            serialisationSettings.Converters.Remove(NodaConverters.LocalDateConverter);
            serialisationSettings.Converters.Add(new NodaPatternConverter<LocalDate>(LocalDatePattern.CreateWithInvariantCulture("dd MMMM yyyy")));

            //Exception on ShortDate property
            var deserialisedExample2 = JsonConvert.DeserializeObject<ExampleDatedModel>(exampleJsonString, serialisationSettings);
        }
    }
}

Using the default serialiser gives throws an exception on the LongDate property:

An unhandled exception of type 'NodaTime.Text.UnparsableValueException' occurred in Newtonsoft.Json.dll

Additional information: The value string does not match the required number from the format string "yyyy". Value being parsed: '^01 January 2017'. (^ indicates error position.)

Replacing with a custom pattern converter throws an exception on the ShortDate property:

An unhandled exception of type 'NodaTime.Text.UnparsableValueException' occurred in Newtonsoft.Json.dll

Additional information: The value string does not match a simple character in the format string " ". Value being parsed: '20^17-01-01'. (^ indicates error position.)

In principle I think I could use two different converters for the two properties, such as

class ExampleDatedModel
{
    [JsonConverter(typeof(ShortDateConverter)]
    public LocalDate ShortDate { get; set; }

    [JsonConverter(typeof(LongDateConverter)]
    public LocalDate LongDate { get; set; }
}

However I don't see how to use NodaTime's NodaPatternConverter with the attribute as you can't instantiate the converter with a pattern.

The documentation helpfully says "Custom converters can be created easily from patterns using NodaPatternConverter." but doesn't give any examples!

Possible solutions I've though about are

  • Create a pair of converters derived from NodaPatternConverter which are configured for the two patterns.
    • NodaPatternConverter is sealed, so can't be inherited from.
  • Create a pair of converters derived from JsonConverter to deal with both patterns
    • I guess these would call two versions of LocalDatePattern.Parse with the different patterns internally.
    • Reimplementation of the whole of JsonConverter seems overkill.
  • Deserialise the dates as strings then convert the dates afterwards.
    • Needs implementing for each class which has a mixture of types.
    • Makes generic methods fetching resources from the API more complex.

But I hope I'm just missing a way of marking up the resource classes to use existing converters.

This does indeed seem to be a use case we hadn't considered. For "normal" usage, sealing NodaPatternConverter feels like the right approach - but when a JsonConverter has to be specified by type rather than instantiated, the sealing is frustrating. I've filed an issue to fix this in 2.0, which I'm hoping to release in the next month or so. (It's now implemented - the pull request shows sample usage as well.)

However, for the meantime I probably would just fork NodaPatternConverter - and add a comment saying that it's only there until you can use 2.0.

You may want to trim it down a bit, as you probably don't need the extra validation, assuming you control all the code that's going to serialize data - if you don't need to worry about non-ISO LocalDate values, you probably don't need validation.

The other aspect is that if you're only ever parsing using the converters, you don't need the writing side at all - you could just throw an exception for that at the moment, potentially.

An alternative to unsealing NodaPatternConverter is to have a simple (abstract) DelegatingConverterBase type, which delegates to another JsonConverter . Typical usage would be something like:

public sealed class ShortDateConverter : DelegatingConverterBase
{
    public ShortDateConverter() : base(NodaConverters.LocalDate) {}
}

That might be a more elegant separation of concerns - and implementable with less code until it's part of Noda Time, too:

public abstract class DelegatingConverterBase : JsonConverter
{
    private readonly JsonConverter original;

    protected DelegatingConverterBase(JsonConverter original)
    {
        this.original = original;
    }

    public override void WriteJson(
        JsonWriter writer, object value, JsonSerializer serializer) =>
        original.WriteJson(writer, value, serializer);

    public override object ReadJson(
        JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) =>
        original.ReadJson(reader, objectType, existingValue, serializer);

    public override bool CanRead => original.CanRead;

    public override bool CanWrite => original.CanWrite;

    public override bool CanConvert(Type objectType) => original.CanConvert(objectType);
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM