Deserialise Json Timestamp with System.Text.Json
Asked Answered
A

5

6

I am trying to deserialise the following JSON

{"serverTime":1613967667240}

into an object of the following class

public class ApiServerTime
{
    [JsonPropertyName("serverTime")]
    public DateTime ServerTime
    {
        get;
        private set;
    }
}

with the following command:

JsonSerializer.Deserialize<ApiServerTime>(jsonString);

but the resulting object contains the ServerTime == DateTime.MinValue. What am I doing wrong?

Acidulous answered 22/2, 2021 at 5:24 Comment(2)
Does this answer your question? How to deserialize a unix timestamp (μs) to a DateTime from JSON?Muzhik
@KenTsu I found that but it's related to Newtonsoft.Json. I am using System.Text.JsonAcidulous
K
8

You can register custom date formatters for System.Text.Json also. https://learn.microsoft.com/en-us/dotnet/standard/datetime/system-text-json-support

public class DateTimeConverterForCustomStandardFormatR : JsonConverter<DateTime>
{
    public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        return DateTime.UnixEpoch.AddMilliseconds(reader.GetInt64());
    }

    public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options)
    {
        // The "R" standard format will always be 29 bytes.
        Span<byte> utf8Date = new byte[29];

        bool result = Utf8Formatter.TryFormat(value, utf8Date, out _, new StandardFormat('R'));
        Debug.Assert(result);

        writer.WriteStringValue(utf8Date);
    }
}


string js = "{\"ServerTime\":1613967667240}";
JsonSerializerOptions options = new JsonSerializerOptions();
options.Converters.Add(new DateTimeConverterForCustomStandardFormatR());
var value = JsonSerializer.Deserialize<ApiServerTime>(js, options);
Klingel answered 22/2, 2021 at 8:32 Comment(0)
M
6

In the spirit of building a better mouse trap, here's an implementation that supports..

  • Unix time in seconds OR milliseconds based on whether the value in seconds can represented within .net DateTime min/max range (1/1/0001 to 31/12/9999).
  • Nullable DateTime to gracefully cater for invalid, empty, and null values.
public class UnixToNullableDateTimeConverter : JsonConverter<DateTime?>
{
    public bool? IsFormatInSeconds { get; init; }

    public override DateTime? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        try
        {
            if (reader.TryGetInt64(out var time))
            {
                // if 'IsFormatInSeconds' is unspecified, then deduce the correct type based on whether it can be represented as seconds within the .net DateTime min/max range (1/1/0001 to 31/12/9999)
                // - because we're dealing with a 64bit value, the unix time in seconds can exceed the traditional 32bit min/max restrictions (1/1/1970 to 19/1/2038)
                if (IsFormatInSeconds == true || IsFormatInSeconds == null && time > _unixMinSeconds && time < _unixMaxSeconds)
                    return DateTimeOffset.FromUnixTimeSeconds(time).LocalDateTime;
                return DateTimeOffset.FromUnixTimeMilliseconds(time).LocalDateTime;
            }
        }
        catch
        {
            // despite the method prefix 'Try', TryGetInt64 will throw an exception if the token isn't a number.. hence we swallow it and return null
        }
        
        return null;
    }

    // write is out of scope, but this could be implemented via writer.ToUnixTimeMilliseconds/WriteNullValue
    public override void Write(Utf8JsonWriter writer, DateTime? value, JsonSerializerOptions options) => throw new NotSupportedException();

    private static readonly long _unixMinSeconds = DateTimeOffset.MinValue.ToUnixTimeSeconds(); // -62_135_596_800
    private static readonly long _unixMaxSeconds = DateTimeOffset.MaxValue.ToUnixTimeSeconds(); // 253_402_300_799
}
Meit answered 4/12, 2021 at 11:24 Comment(0)
M
1
"\"\\/Date(1335205592410)\\/\""         .NET JavaScriptSerializer
"\"\\/Date(1335205592410-0500)\\/\""    .NET DataContractJsonSerializer
"2012-04-23T18:25:43.511Z"              JavaScript built-in JSON object
"2012-04-21T18:25:43-05:00"             ISO 8601

{
    "Date": "\/Date(1580803200000-0800)\/"
}
  • at last, you can use temporary model to save current timestamp, then convert it.
public class ApiServerTime{
public long ServerTime{get;set;}
public static DateTime UnixTimeStampToDateTime( double unixTimeStamp )
{
    // Unix timestamp is seconds past epoch
    System.DateTime dtDateTime = new DateTime(1970,1,1,0,0,0,0,System.DateTimeKind.Utc);
    dtDateTime = dtDateTime.AddSeconds( unixTimeStamp ).ToLocalTime();
    return dtDateTime;
}
}


Muzhik answered 22/2, 2021 at 7:21 Comment(1)
"you had better to use ISO 8601": that is a bit irrelevant, the question is not asking for format recommendations. And the choice is not on the one who deserializes, but on the one who serializes, which is frequently a third party unlikely to change the format.Crammer
G
1

Here's an implementation from Microsoft's page on the topic, where date is in the format {"serverTime": "/Date(1590863400000)/"} or {"serverTime": "/Date(1590863400000-0700)/"}:

sealed class UnixEpochDateTimeConverter : JsonConverter<DateTime>
{
    static readonly DateTime s_epoch = new DateTime(1970, 1, 1, 0, 0, 0);
    static readonly Regex s_regex = new Regex("^/Date\\(([+-]*\\d+)\\)/$", RegexOptions.CultureInvariant);

    public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        string formatted = reader.GetString()!;
        Match match = s_regex.Match(formatted);

        if (!match.Success
            || !long.TryParse(match.Groups[1].Value, System.Globalization.NumberStyles.Integer, CultureInfo.InvariantCulture, out long unixTime))
        {
            throw new JsonException();
        }

        return s_epoch.AddMilliseconds(unixTime);
    }

    public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options)
    {
        long unixTime = Convert.ToInt64((value - s_epoch).TotalMilliseconds);

        string formatted = string.Create(CultureInfo.InvariantCulture, $"/Date({unixTime})/");
        writer.WriteStringValue(formatted);
    }
}
Goshawk answered 12/2 at 21:16 Comment(0)
F
0

Here is a simple but complete example:

using System.Text.Json;
using System.Text.Json.Serialization;

var person = new Person { Name = "BaiJiFeiLong", Birthday = DateTime.Now };
var json = JsonSerializer.Serialize(person);
Console.WriteLine($"Json: {json}");
person = JsonSerializer.Deserialize<Person>(json);
Console.WriteLine($"Person: [{person}]");

internal class Person
{
    public string Name { get; set; } = "";

    [JsonConverter(typeof(UnixMilliDateTimeConverter))]
    public DateTime Birthday { get; set; }

    public override string ToString()
    {
        return $"{nameof(Name)}: {Name}, {nameof(Birthday)}: {Birthday}";
    }
}

internal class UnixMilliDateTimeConverter : JsonConverter<DateTime>
{
    public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        return DateTimeOffset.FromUnixTimeMilliseconds(reader.GetInt64()).LocalDateTime;
    }

    public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options)
    {
        writer.WriteNumberValue(((DateTimeOffset)value).ToUnixTimeMilliseconds());
    }
}

The output

Json: {"Name":"BaiJiFeiLong","Birthday":1720078188334}
Person: [Name: BaiJiFeiLong, Birthday: 2024/7/4 15:29:48]
Florio answered 4/7 at 7:29 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.