如何让 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"
}
]
几件事:
1595357722990
是一个毫秒精度的Unix时间戳,等于2020-07-21 18:55:22.990 UTC。您检查它的网站正在将其转换为您的当地时间。 Here's a fiddle showing the conversion with .NET code.,这里是 JavaScript:
const dt = new Date(1595357722990);
console.log(dt.toISOString());
你真的应该避免 JavaScriptSerializer
。 The 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);
}
我在 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"
}
]
几件事:
1595357722990
是一个毫秒精度的Unix时间戳,等于2020-07-21 18:55:22.990 UTC。您检查它的网站正在将其转换为您的当地时间。 Here's a fiddle showing the conversion with .NET code.,这里是 JavaScript:
const dt = new Date(1595357722990);
console.log(dt.toISOString());
你真的应该避免
JavaScriptSerializer
。 The 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, useNewtonsoft.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);
}