首行是 NULL 时如何阻止 JSON 反序列化转换 DataSet 日期值
结论
要让 DATER 保持字符串类型,并完整保留 JSON 中的 "2026-02-16T11:17:43.803",需要设置:
DateParseHandling = DateParseHandling.None
DateFormatHandling.IsoDateFormat 控制的是日期序列化后的输出格式,无法阻止反序列化器将 ISO 8601 字符串解析为 DateTime。
如果旧程序要求 DATER 始终为 DateTime,不要让 DataSet 根据第一行数据推断列类型。可以先明确数据结构,再创建类型固定的 DataColumn。
保留原始日期字符串
设置方式如下:
using Newtonsoft.Json;
using System.Data;
string sTest =
"{\"Table1\":[{\"DATER\":null},{\"DATER\":\"2026-02-16T11:17:43.803\"}]}";
var settings = new JsonSerializerSettings
{
DateParseHandling = DateParseHandling.None
};
DataSet moDataSet =
JsonConvert.DeserializeObject<DataSet>(sTest, settings);
string columnType =
moDataSet.Tables[0].Columns["DATER"].DataType.Name;
object firstValue =
moDataSet.Tables[0].Rows[0]["DATER"];
object secondValue =
moDataSet.Tables[0].Rows[1]["DATER"];
预期结果为:
columnType = "String"
firstValue = DBNull.Value
secondValue = "2026-02-16T11:17:43.803"
设置 DateParseHandling.None 后,JSON.NET 会把日期文本作为普通字符串读取,不会先转成 DateTime,再根据当前区域设置转换成 "02/16/2026 11:17:43" 这样的字符串。
为什么首行是 null 会改变结果
JSON 没有 DateTime 数据类型。下面这个值在 JSON 中只是字符串:
"2026-02-16T11:17:43.803"
JSON.NET 的读取器默认会识别符合日期格式的字符串,并将其转换为 DateTime token。DataSet 转换器则需要根据数据内容推断 DataColumn.DataType。
第一行有日期值时,转换器可以将该列推断为 DateTime。第一行是 null 时,转换器缺少判断列类型的信息,后续可能会把该列创建为 String。如果后面的 ISO 日期已经被读取器解析为 DateTime,写入字符串列时还会发生一次从 DateTime 到字符串的转换。转换结果可能受到当前区域设置影响,毫秒部分也可能丢失。
这里涉及两个处理环节:
JsonTextReader是否将 ISO 字符串解析为DateTime。DataSet转换器如何推断DataColumn.DataType。
DateParseHandling.None 作用于第一个环节,用来避免原始字符串被提前解析。
如果旧程序要求该列始终为 DateTime
保留原始字符串和让列类型始终为 DateTime 是两项不同的要求。DateTime 值本身不保存 "2026-02-16T11:17:43.803" 这样的显示格式,只有将日期转换为字符串时才需要指定格式。
如果旧代码需要直接执行:
DateTime value = (DateTime)row["DATER"];
应显式定义列类型,不再让 DataSet 根据第一行数据推断。可以先将 JSON 反序列化为强类型对象,再创建 DataTable:
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Data;
public sealed class ApiResult
{
public List<ApiRow> Table1 { get; set; }
}
public sealed class ApiRow
{
public DateTime? DATER { get; set; }
}
转换代码如下:
string json =
"{\"Table1\":[{\"DATER\":null},{\"DATER\":\"2026-02-16T11:17:43.803\"}]}";
ApiResult result = JsonConvert.DeserializeObject<ApiResult>(json);
var table = new DataTable("Table1");
DataColumn daterColumn = table.Columns.Add("DATER", typeof(DateTime));
daterColumn.AllowDBNull = true;
foreach (ApiRow item in result.Table1)
{
DataRow row = table.NewRow();
row["DATER"] = item.DATER.HasValue
? (object)item.DATER.Value
: DBNull.Value;
table.Rows.Add(row);
}
var dataSet = new DataSet();
dataSet.Tables.Add(table);
无论第一行是否为 null,下面的判断都会成立:
dataSet.Tables[0].Columns["DATER"].DataType == typeof(DateTime)
空值使用 DBNull.Value,非空值则保存为实际的 DateTime。
再次输出 ISO 格式时,需要明确指定格式:
DateTime value =
(DateTime)dataSet.Tables[0].Rows[1]["DATER"];
string text = value.ToString(
"yyyy-MM-dd'T'HH:mm:ss.fff",
System.Globalization.CultureInfo.InvariantCulture);
结果为:
2026-02-16T11:17:43.803
注意事项
DateFormatHandling不能替代DateParseHandling。前者控制日期的序列化表示,后者决定读取 JSON 时是否自动识别日期。- 如果 REST API 可能返回带时区的值,例如
Z或+08:00,可根据接口语义使用DateTimeOffset?,以免丢失偏移量信息。 - 示例 2 中仍然只有一个名为
Table1的表,因此应通过Tables[0].Rows[1]查询非空值,不能使用Tables[1].Rows[0]。 - 只需保留原始文本时,使用
DateParseHandling.None。需要兼容旧程序中的DateTime列时,应采用固定 schema 或强类型中间对象。
备注:内容仅供参考。
