如何让 Newtonsoft 按原样反序列化日期并将其称为 utc?

How do I get Newtonsoft to deserialize a date as it is and call it utc?

我在 sql 数据库中有一个日期时间:2020-07-21 13:55:22.990 虽然这个日期时间是 UTC(如果我错了请纠正我),但不会有知道在数据库范围内的方法。作为参考,我在 CST(UTC-6),这个问题是在夏令时写的(所以我现在是 UTC 的 -5)。

我正在向该数据库发送一个查询,然后使用以下方法返回 json(我认为它没有任何问题,但我将其包括在内以防万一):

private static string GetJSONFromSQLQuery(string query, string connectionString)
{
    var dataTable = new DataTable();

    using (var sqlConnection = new SqlConnection(connectionString))
    using (var sqlCommand = new SqlCommand(query, sqlConnection))
    using (var sqlDataAdapter = new SqlDataAdapter(sqlCommand))
    {
        sqlConnection.Open();
        sqlDataAdapter.Fill(dataTable);
    }

    var rowsAsDictionaries = new List<Dictionary<string, object>>();
    foreach (DataRow row in dataTable.Rows)
    {
        var rowAsDictionary = new Dictionary<string, object>();

        // ReSharper disable once LoopCanBeConvertedToQuery
        foreach (DataColumn column in dataTable.Columns)
            rowAsDictionary.Add(column.ColumnName, row[column]);

        rowsAsDictionaries.Add(rowAsDictionary);
    }

    return new JavaScriptSerializer().Serialize(rowsAsDictionaries);
}

这是我返回的 json 的片段(在实际调用中,对象中会有更多属性,通常更多对象):


[
    {
        "Timestamp":"\/Date(1595357722990)\/",
    },
]

https://www.freeformatter.com/epoch-timestamp-to-date-converter.html 说这是 7/21/2020,1:55:22 PM 'Convert epoch timestamp to date'

然后我用 newtonsoft 反序列化 json:

var deserializedJson = JsonConvert.DeserializeObject<List<MyObjectType>>(json)

MyObjectType 有一堆 { get;放; } 属性,其中之一是:

public DateTime Timestamp { get; set; }

根据上面的 json 片段和 newtonsoft 调用,时间戳将设置为 7/21,6:55:22 并且种类 属性 将设置为 Utc。这不正确不是我所期望的。

经过一番搜索后,我了解了 JsonSerializerSettings 中的 DateTimeZoneHandling 属性,并对 4 个枚举选项中的每一个进行了以下调用:

var local = JsonConvert.DeserializeObject<List<MyObjectType>>(json, new JsonSerializerSettings
{
    DateTimeZoneHandling = DateTimeZoneHandling.Local,

});

var roundtrip = JsonConvert.DeserializeObject<List<MyObjectType>>(json, new JsonSerializerSettings
{
    DateTimeZoneHandling = DateTimeZoneHandling.RoundtripKind,

});

var unspecified = JsonConvert.DeserializeObject<List<MyObjectType>>(json, new JsonSerializerSettings
{
    DateTimeZoneHandling = DateTimeZoneHandling.Unspecified,

});

var utc = JsonConvert.DeserializeObject<List<MyObjectType>>(json, new JsonSerializerSettings
{
    DateTimeZoneHandling = DateTimeZoneHandling.Utc,

});

这在 MyObjectType 中提供了以下 DateTime 对象(注意这些是 DateTime 对象的片段):

deserializedJson:
    Time: 7/21 6:55:22,
    Kind: Utc

local:
    Time: 7/21 1:55:22,
    Kind: Local
    
roundtripKind:
    Time: 7/21 6:55:22,
    Kind: Utc
    
unspecified:
    Time: 7/21 6:55:22,
    Kind: Unspecified

utc:
    Time: 7/21 6:55:22,
    Kind: Utc

Kind属性特别重要因为我会打电话给

TimeZoneInfo.ConvertTimeFromUtc(theDeserializedTimeStamp, TimeZoneInfo.Local);

我想要的输出是:

Time: 7/21 1:55:22,
Kind: Utc

因为这将反映数据库中的内容,我将能够调用 TimeZoneInfo.ConvertTimeFromUtc 将 UTC 时间转换为本地时区。

如何获得该输出?

编辑:

我找到了解决方法。我将 GetJSONFromSQLQuery 中的 return 语句更改为:

return JsonConvert.SerializeObject(rowsAsDictionaries);

并且所有 DateTimeZoneHandling 选项都将时间保留为 1:55 PM,并且 Kind 属性 的结果各不相同(但符合预期)。不确定为什么来自 JavaScriptSerializer 的 json 不能很好地播放,但至少我现在已经解脱了。

JsonConvert.SerializeObject的json长这样:

[
   {
      "Timestamp":"2020-07-21T13:55:22.99"
   }
]

几件事:

const dt = new Date(1595357722990);
console.log(dt.toISOString());

  • 你真的应该避免 JavaScriptSerializerThe docs 说:

    Important

    For .NET Framework 4.7.2 and later versions, the APIs in the System.Text.Json namespace should be used for serialization and deserialization. For earlier versions of .NET Framework, use Newtonsoft.Json.

  • 格式 "\/Date(1595357722990)\/" 通常被称为“ASP.NET JSON 日期”,因为它是在 [= 的早期版本中首次发布到世界上的=106=],大约在 JSON 开始变得比 XML 更普遍的时候。那是一种糟糕的格式,现在也很糟糕。参见 Scott Hanselman 于 2012 年发表的 On the nightmare that is JSON Dates...

  • 您应该改用 ISO 8601 extended date+time format (see also RFC 3339),这是大多数现代 JSON 实现的默认设置,包括 Newtosoft JSON.Net 和 System.Text.Json

  • 你说:

    Timestamp would be set to 7/21, 6:55:22 and the Kind property would be set to Utc. This is incorrect.

    不,它实际上是正确的 - 假设您指的是 PM。这就是 1595357722990 所代表的价值。可能您 创建 该值不正确(请参阅下文)。

  • 在大多数情况下,包括您的情况,您不需要更改 JSON.Net 的默认 DateTimeZoneHandling 选项。

  • 您说您想要的输出是 DateTimeKind.Utc - 但您的意思似乎是 转换为当地时间之后?那没有意义。如果您要转换为本地时间,则生成的类型将为 DateTimeKind.Local.

  • 在这种情况下使用 TimeZoneInfo.ConvertTime 就可以了。由于输入种类已设置为 DateTimeKind.Utc,因此不需要 ...FromUtc 部分。 (只有当输入类型为 DateTimeKind.Unspecified 时才真正重要。)

  • 查看您的编辑,您似乎实际上将 2020-07-21T13:55:22.99 存储在您的数据库中。如果该字段旨在被解释为 UTC,那么您确实应该在结果 JSON 中看到 13 小时。但是,它还应该包含尾随 Z 以指示数据是 UTC。

    你的 JSON 实际上应该是这样的:

    [
        {
            "Timestamp":"2020-07-21T13:55:22.990Z"
        }
    ]
    

    默认情况下可能会有一些额外的小数位,这没关系。

  • 你没有得到那个结果的原因,以及为什么 JavaScriptSerializer 创建的原始值是错误的,是因为你没有在值上设置 DateTimeKind.Utc从数据库中读取。您需要使用 DateTime.SpecifyKind 更改代码以明确执行此操作。例如,您可以将内部循环修改为:

    foreach (DataColumn column in dataTable.Columns)
    {
        object o = row[column];
        if (o is DateTime)
        {
            o = DateTime.SpecifyKind((DateTime)o, DateTimeKind.Utc);
        }
    
        rowAsDictionary.Add(column.ColumnName, o);
    }
    
  • 总的来说,您的代码中有很多是次优的。实际上,您应该使用结构化 class 的可枚举列表或列表而不是对象字典,并且您应该使用 DataReader 而不是 DataTable。此外,在大多数框架中,例如在 ASP.net 中,您应该 return 直接列出并让框架进行 JSON 转换。换句话说,您可能根本不应该在此方法中转换为 JSON。

好久不见了。没有其他人给出答案,我已经使用我在 Edit 中发布的解决方案很长一段时间了。所以,我要把它标记为答案。

最终,我使用的序列化器又旧又不好。我以为我用的是Newtonsoft,但是我错了

方法需要改成这样:

private static string GetJSONFromSQLQuery(string query, string connectionString)
{
    var dataTable = new DataTable();

    using (var sqlConnection = new SqlConnection(connectionString))
    using (var sqlCommand = new SqlCommand(query, sqlConnection))
    using (var sqlDataAdapter = new SqlDataAdapter(sqlCommand))
    {
        sqlConnection.Open();
        sqlDataAdapter.Fill(dataTable);
    }

    var rowsAsDictionaries = new List<Dictionary<string, object>>();
    foreach (DataRow row in dataTable.Rows)
    {
        var rowAsDictionary = new Dictionary<string, object>();

        // ReSharper disable once LoopCanBeConvertedToQuery
        foreach (DataColumn column in dataTable.Columns)
            rowAsDictionary.Add(column.ColumnName, row[column]);

        rowsAsDictionaries.Add(rowAsDictionary);
    }

    // The original return statement:
    //return new JavaScriptSerializer().Serialize(rowsAsDictionaries);

    // The correct return statement:
    return JsonConvert.SerializeObject(rowsAsDictionaries);
}

正如 PanagiotisKanavos 所说:

new JavaScriptSerializer() is not JSON.NET. It's an obsolete .NET serialised used as a stop-gap. It had a lot of quirks so people avoided it when possible. ASP.NET WebAPI and .NET Core up to 2.2 uses JSON.NET. .NET Core 3.1 uses a new JSON serialised that's even stricter



此外,有点超出范围,但如果有人想要异步方法,这个可行:

private static async Task<string> GetJSONFromSQLQueryAsync(string query, string connectionString)
{
    var dataTable = new DataTable();

    using (var sqlConnection = new SqlConnection(connectionString))
    {
        await sqlConnection.OpenAsync();
        using var sqlCommand = new SqlCommand(query, sqlConnection);
        using var reader = await sqlCommand.ExecuteReaderAsync();

        dataTable.Load(reader);
    }

    var rowsAsDictionaries = new List<Dictionary<string, object>>();
    foreach (DataRow row in dataTable.Rows)
    {
        var rowAsDictionary = new Dictionary<string, object>();

        // ReSharper disable once LoopCanBeConvertedToQuery
        foreach (DataColumn column in dataTable.Columns)
            rowAsDictionary.Add(column.ColumnName, row[column]);

        rowsAsDictionaries.Add(rowAsDictionary);
    }

    return JsonConvert.SerializeObject(rowsAsDictionaries);
}