JamesNK / Newtonsoft.Json

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

JArray.Parse does not recognize all valid RFC3339 DateTimes #2310

Open Paqi opened 4 years ago

Paqi commented 4 years ago

Using Newtonsoft.Json (12.0.3)

Source/destination types

N/A

Source JSON

[
    {
        "observationTime": "2019-10-19T14:43:00.0000000Z",
        "sensor1": 342.0
    },
    {
        "observationTime": "2019-10-19t14:43:00.0000000Z",
        "sensor1": 342.0
    },
    {
        "observationTime": "2019-10-19 14:43:00.0000000Z",
        "sensor1": 342.0
    }
]

Expected behavior

The "observationTime" property in each JObject should all be deserialized into Date types.

Actual behavior

The latter two objects' "observationTime" properties are deserialized into String types, rather than Date types. I'm guessing it's because the separator between Full-Date and Full-Time is a lowercase t and whitespace, respectively. These are valid formats, per RFC3339 Section 5.6

 NOTE: Per [ABNF] and ISO8601, the "T" and "Z" characters in this
      syntax may alternatively be lower case "t" or "z" respectively.
NOTE: ISO 8601 defines date and time separated by "T".
      Applications using this syntax may choose, for the sake of
      readability, to specify a full-date and full-time separated by
      (say) a space character.

Steps to reproduce

using System;
using Newtonsoft.Json.Linq;

namespace TestNewtonSoftVersion
{
    class Program
    {
        static void Main(string[] args)
        {
            string validPayload = @"[
    {
        ""observationTime"": ""2019-10-19T14:43:00.0000000Z"",
        ""sensor1"": 342.0
    },
    {
        ""observationTime"": ""2019-10-19t14:43:00.0000000Z"",
        ""sensor1"": 342.0
    },
    {
        ""observationTime"": ""2019-10-19 14:43:00.0000000Z"",
        ""sensor1"": 342.0
    }
]";

            JArray jarray = JArray.Parse(validPayload);
            foreach (JObject obj in jarray)
            {
                Console.WriteLine(obj["observationTime"].Type.ToString());
            }
            //OUTPUT
            //Date
            //String
            //String
        }
    }
}
Paqi commented 4 years ago

C#'s DateTime class handles all these cases. See the following Linqpad script:

string validPayload = @"[
{
    ""observationTime"": ""2019-10-19T14:43:00.0000000Z"",
    ""sensor1"": 342.0
},
{
    ""observationTime"": ""2019-10-19t14:43:00.0000000Z"",
    ""sensor1"": 342.0
},
{
    ""observationTime"": ""2019-10-19 14:43:00.0000000Z"",
    ""sensor1"": 342.0
}
]";

DateTime date1 = DateTime.Parse("2019-10-19T14:43:00.0000000Z");
DateTime date2 = DateTime.Parse("2019-10-19t14:43:00.0000000Z");
DateTime date3 = DateTime.Parse("2019-10-19 14:43:00.0000000Z");

date1.ToString("o").Dump();
date2.ToString("o").Dump();
date3.ToString("o").Dump();

/*OUTPUT
2019-10-19T14:43:00.0000000+00:00
2019-10-19T14:43:00.0000000+00:00
2019-10-19T14:43:00.0000000+00:00
*/
Paqi commented 4 years ago

Here's the workaround I'm using for now:

JArray jarray = JsonConvert.DeserializeObject<JArray>(validPayload, new JsonSerializerSettings {DateTimeZoneHandling = DateTimeZoneHandling.Utc});
foreach(JObject obj in jarray)
{
    if(obj["observationTime"].Type == JTokenType.String)
    {
        obj["observationTime"] = DateTime.SpecifyKind(DateTime.Parse(obj["observationTime"].ToObject<string>()), DateTimeKind.Utc);
    }
    else if(obj["observationTime"].Type != JTokenType.Date)
    {
        //Handle Error Case
    }
}