我有一个具有两列的DataTable。ShipmentDate(DateTime)和Count(Int)。在对字符串反序列化之后,我发现如果第一个itemarray值为null,则ShipmentDate的类型将变为字符串。
检查以下示例。除了第一个数组项,两个json字符串都具有相同的数据。
string jsonTable1 = "[{\"ShipmentDate\":null,\"Count\":3},{\"ShipmentDate\":\"2015-05-13T00:00:00\",\"Count\":13},{\"ShipmentDate\":\"2015-05-19T00:00:00\",\"Count\":1},{\"ShipmentDate\":\"2015-05-26T00:00:00\",\"Count\":1},{\"ShipmentDate\":\"2015-05-28T00:00:00\",\"Count\":2}]";
string jsonTable2 = "[{\"ShipmentDate\":\"2015-05-13T00:00:00\",\"Count\":13},{\"ShipmentDate\":null,\"Count\":3},{\"ShipmentDate\":\"2015-05-19T00:00:00\",\"Count\":1},{\"ShipmentDate\":\"2015-05-26T00:00:00\",\"Count\":1},{\"ShipmentDate\":\"2015-05-28T00:00:00\",\"Count\":2}]";
DataTable tbl1 = Newtonsoft.Json.JsonConvert.DeserializeObject<DataTable>(jsonTable1);
DataTable tbl2 = Newtonsoft.Json.JsonConvert.DeserializeObject<DataTable>(jsonTable2);
Console.WriteLine(tbl1.Columns["ShipmentDate"].DataType);
Console.WriteLine(tbl2.Columns["ShipmentDate"].DataType);
在我的场景中,第一个项目数组的ShipmentDate可以为null,并且将其转换为字符串类型会产生问题。
我有一个数据表的架构是动态的情况。我无法创建强类型的类。
这里的基本问题是Json.NET仅通过查看 第一行中
存在的令牌值来DataTableConverter
推断每个DataColumn.DataType
令牌。之所以这样工作,是因为它将流表的JSON而不是将整个数据加载到中间层次结构中。流式传输可以在减少内存使用的情况下提供更好的性能,但这意味着第一行中的值可能会导致列的类型不正确。
__JToken
null
这是不时出现在stackoverflow上的问题,例如在反序列化缺少第一列的数据表的问题中。在这种情况下,发问者会事先知道列类型应为double
。在您的情况下,您已声明 datatable的模式是dynamic
,因此无法使用答案。但是,与该问题一样,由于Json.NET是根据MIT许可证开放的源代码,因此可以DataTableConverter
使用必要的逻辑创建其修改版本。
事实证明,可以通过记住具有歧义数据类型的列,然后在确定合适的类型时用正确键入的列替换这些列,从而在保留流行为的同时正确设置列类型:
/// <summary>
/// Converts a <see cref="DataTable"/> to and from JSON.
/// </summary>
public class TypeInferringDataTableConverter : Newtonsoft.Json.Converters.DataTableConverter
{
// Adapted from https://github.com/JamesNK/Newtonsoft.Json/blob/master/Src/Newtonsoft.Json/Converters/DataTableConverter.cs
// Original license: https://github.com/JamesNK/Newtonsoft.Json/blob/master/LICENSE.md
/// <summary>
/// Reads the JSON representation of the object.
/// </summary>
/// <param name="reader">The <see cref="JsonReader"/> to read from.</param>
/// <param name="objectType">Type of the object.</param>
/// <param name="existingValue">The existing value of object being read.</param>
/// <param name="serializer">The calling serializer.</param>
/// <returns>The object value.</returns>
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null)
{
return null;
}
DataTable dt = existingValue as DataTable;
if (dt == null)
{
// handle typed datasets
dt = (objectType == typeof(DataTable))
? new DataTable()
: (DataTable)Activator.CreateInstance(objectType);
}
// DataTable is inside a DataSet
// populate the name from the property name
if (reader.TokenType == JsonToken.PropertyName)
{
dt.TableName = (string)reader.Value;
reader.ReadAndAssert();
if (reader.TokenType == JsonToken.Null)
{
return dt;
}
}
if (reader.TokenType != JsonToken.StartArray)
{
throw JsonSerializationExceptionHelper.Create(reader, "Unexpected JSON token when reading DataTable. Expected StartArray, got {0}.".FormatWith(CultureInfo.InvariantCulture, reader.TokenType));
}
reader.ReadAndAssert();
var ambiguousColumnTypes = new HashSet<string>();
while (reader.TokenType != JsonToken.EndArray)
{
CreateRow(reader, dt, serializer, ambiguousColumnTypes);
reader.ReadAndAssert();
}
return dt;
}
private static void CreateRow(JsonReader reader, DataTable dt, JsonSerializer serializer, HashSet<string> ambiguousColumnTypes)
{
DataRow dr = dt.NewRow();
reader.ReadAndAssert();
while (reader.TokenType == JsonToken.PropertyName)
{
string columnName = (string)reader.Value;
reader.ReadAndAssert();
DataColumn column = dt.Columns[columnName];
if (column == null)
{
bool isAmbiguousType;
Type columnType = GetColumnDataType(reader, out isAmbiguousType);
column = new DataColumn(columnName, columnType);
dt.Columns.Add(column);
if (isAmbiguousType)
ambiguousColumnTypes.Add(columnName);
}
else if (ambiguousColumnTypes.Contains(columnName))
{
bool isAmbiguousType;
Type newColumnType = GetColumnDataType(reader, out isAmbiguousType);
if (!isAmbiguousType)
ambiguousColumnTypes.Remove(columnName);
if (newColumnType != column.DataType)
{
column = ReplaceColumn(dt, column, newColumnType, serializer);
}
}
if (column.DataType == typeof(DataTable))
{
if (reader.TokenType == JsonToken.StartArray)
{
reader.ReadAndAssert();
}
DataTable nestedDt = new DataTable();
var nestedUnknownColumnTypes = new HashSet<string>();
while (reader.TokenType != JsonToken.EndArray)
{
CreateRow(reader, nestedDt, serializer, nestedUnknownColumnTypes);
reader.ReadAndAssert();
}
dr[columnName] = nestedDt;
}
else if (column.DataType.IsArray && column.DataType != typeof(byte[]))
{
if (reader.TokenType == JsonToken.StartArray)
{
reader.ReadAndAssert();
}
List<object> o = new List<object>();
while (reader.TokenType != JsonToken.EndArray)
{
o.Add(reader.Value);
reader.ReadAndAssert();
}
Array destinationArray = Array.CreateInstance(column.DataType.GetElementType(), o.Count);
Array.Copy(o.ToArray(), destinationArray, o.Count);
dr[columnName] = destinationArray;
}
else
{
object columnValue = (reader.Value != null)
? serializer.Deserialize(reader, column.DataType) ?? DBNull.Value
: DBNull.Value;
dr[columnName] = columnValue;
}
reader.ReadAndAssert();
}
dr.EndEdit();
dt.Rows.Add(dr);
}
static object RemapValue(object oldValue, Type newType, JsonSerializer serializer)
{
if (oldValue == null)
return null;
if (oldValue == DBNull.Value)
return oldValue;
return JToken.FromObject(oldValue, serializer).ToObject(newType, serializer);
}
private static DataColumn ReplaceColumn(DataTable dt, DataColumn column, Type newColumnType, JsonSerializer serializer)
{
var newValues = Enumerable.Range(0, dt.Rows.Count).Select(i => dt.Rows[i]).Select(r => RemapValue(r[column], newColumnType, serializer)).ToList();
var ordinal = column.Ordinal;
var name = column.ColumnName;
var @namespace = column.Namespace;
var newColumn = new DataColumn(name, newColumnType);
newColumn.Namespace = @namespace;
dt.Columns.Remove(column);
dt.Columns.Add(newColumn);
newColumn.SetOrdinal(ordinal);
for (int i = 0; i < dt.Rows.Count; i++)
dt.Rows[i][newColumn] = newValues[i];
return newColumn;
}
private static Type GetColumnDataType(JsonReader reader, out bool isAmbiguous)
{
JsonToken tokenType = reader.TokenType;
switch (tokenType)
{
case JsonToken.Integer:
case JsonToken.Boolean:
case JsonToken.Float:
case JsonToken.String:
case JsonToken.Date:
case JsonToken.Bytes:
isAmbiguous = false;
return reader.ValueType;
case JsonToken.Null:
case JsonToken.Undefined:
isAmbiguous = true;
return typeof(string);
case JsonToken.StartArray:
reader.ReadAndAssert();
if (reader.TokenType == JsonToken.StartObject)
{
isAmbiguous = false;
return typeof(DataTable); // nested datatable
}
else
{
isAmbiguous = false;
bool innerAmbiguous;
// Handling ambiguity in array entries is not yet implemented because the first non-ambiguous entry in the array
// might occur anywhere in the sequence, requiring us to scan the entire array to determine the type,
// e.g., given: [null, null, null, 314, null]
// we would need to scan until the 314 value, and do:
// return typeof(Nullable<>).MakeGenericType(new[] { reader.ValueType }).MakeArrayType();
Type arrayType = GetColumnDataType(reader, out innerAmbiguous);
return arrayType.MakeArrayType();
}
default:
throw JsonSerializationExceptionHelper.Create(reader, "Unexpected JSON token when reading DataTable: {0}".FormatWith(CultureInfo.InvariantCulture, tokenType));
}
}
}
internal static class JsonSerializationExceptionHelper
{
public static JsonSerializationException Create(this JsonReader reader, string format, params object[] args)
{
// Adapted from https://github.com/JamesNK/Newtonsoft.Json/blob/master/Src/Newtonsoft.Json/JsonPosition.cs
var lineInfo = reader as IJsonLineInfo;
var path = (reader == null ? null : reader.Path);
var message = string.Format(CultureInfo.InvariantCulture, format, args);
if (!message.EndsWith(Environment.NewLine, StringComparison.Ordinal))
{
message = message.Trim();
if (!message.EndsWith(".", StringComparison.Ordinal))
message += ".";
message += " ";
}
message += string.Format(CultureInfo.InvariantCulture, "Path '{0}'", path);
if (lineInfo != null && lineInfo.HasLineInfo())
message += string.Format(CultureInfo.InvariantCulture, ", line {0}, position {1}", lineInfo.LineNumber, lineInfo.LinePosition);
message += ".";
return new JsonSerializationException(message);
}
}
internal static class StringUtils
{
// Adapted from https://github.com/JamesNK/Newtonsoft.Json/blob/master/Src/Newtonsoft.Json/Utilities/StringUtils.cs
public static string FormatWith(this string format, IFormatProvider provider, object arg0)
{
return format.FormatWith(provider, new[] { arg0 });
}
private static string FormatWith(this string format, IFormatProvider provider, params object[] args)
{
return string.Format(provider, format, args);
}
}
internal static class JsonReaderExtensions
{
public static void ReadAndAssert(this JsonReader reader)
{
if (reader == null)
throw new ArgumentNullException("reader");
if (!reader.Read())
{
throw JsonSerializationExceptionHelper.Create(reader, "Unexpected end when reading JSON.");
}
}
}
然后像这样使用它:
var settings = new JsonSerializerSettings { Converters = new[] { new TypeInferringDataTableConverter() } };
DataTable tbl1 = Newtonsoft.Json.JsonConvert.DeserializeObject<DataTable>(jsonTable1, settings);
DataTable tbl2 = Newtonsoft.Json.JsonConvert.DeserializeObject<DataTable>(jsonTable2, settings);
不要设置,NullValueHandling = NullValueHandling.Ignore
因为现在可以正确处理空值。
原型小提琴
请注意,尽管此类处理带有null
值的列的重新键入,但不会处理包含第一个数组项为null的包含数组值的列的重新键入。例如,如果某列的第一行具有值
[null, null, null, 314, null]
然后,理想地推断出的列类型将是typeof( long? [] )
,但是此处未实现。可能有必要将JSON完全加载到JToken
层次结构中以进行确定。
问题内容: 我的问题与这一问题非常相似,但是我没有足够的声誉对原始答案发表评论。 我有一个名为FillPDF的自定义类,该类在服务器上进行序列化并在客户端上反序列化。 该班由一个中包含的一个集合属性 通过阅读原始问题的解决方案,我知道了为什么将类型错误地设置为。我了解Json.Net 仅通过查看第一行来推断每个推断(我的第一行具有值)。 我试图从原始问题开始实施解决方案。dbc建议覆盖。我已经做到
是否有一种简单的方法来反序列化可以是或
我有一门课是这样的: 但是当我试图序列化它时,我收到一个错误,上面写着“试图序列化java.lang.class:java.lang.字符串。忘记注册一个类型适配器了吗?”。所以我创建了这个适配器: } 并登记如下: 但我还是犯了同样的错误<我做错了什么 适配器的实现看起来正常吗?
问题内容: 在这一点上,这已经是一个老问题了,我可能已经阅读了有关SO的所有相关主题。 但是要点。我需要一些建议或更正吗? 出于某种原因,我们有两种可生成的Jsons: 和 对象和数组。还有其他参数,但在这里无关紧要。每个请求的“ id”都不同。有时是userId,PortfolioId等。因此我得到“ id”并将其传递给相关的var。 很长一段时间我一直在处理第一种情况。并这样创建POJO: 数
问题内容: 我正在尝试制作一个使用Jackson来反序列化POJO的类。 看起来像这样… 我对此实施有2个问题。 首先是我将类类型传递给方法,以便对象映射器知道应反序列化的类型。有使用泛型的更好方法吗? 同样在get方法中,我将一个从objectMapper返回的对象强制转换为T。这看起来特别讨厌,因为我必须在此处强制转换T,然后还必须从调用它的方法中强制转换对象类型。 我在该项目中使用了Robo
问题内容: 可以说我有以下格式的JSON: 我试图避免自定义反序列化器,并尝试将上述JSON(称为Wrapper.java)反序列化为Java POJO。“类型”字段指示“对象”反序列化,即。type = foo表示使用Foo.java反序列化“ object”字段。(如果type = Bar,则使用Bar.java反序列化对象字段)。元数据/所有者将始终使用相同的反序列化方式,每个方法都使用一个