我們用Newtonsoft.Json.Linq.JObject.GetValue("[key]").ToObject<DataTable>方法把一個JObject對象中的某個節點轉換成DataTable格式的數據很方便。一直這樣用也沒出什么問題。但是最近發現在某些情況下,這種數據轉換其實是有一定的問題甚至報錯。場景如下:
1. 當數據源類似如下時。當把Data節點的數據轉換成DataTable格式時,會默認abc字段為整型,導致在對第二條數據做轉換時,50.11會丟失精度,變成整型50
"{\"Data\":[{\"abc\":50},{\"abc\":50.01}]}"
2. 當數據源類似如下時。就會導致第二條數據在轉化成INT類型時出錯。
"{\"Data\":[{\"abc\":50},{\"abc\":\"xyz\"}]}"
這個就需要用到ToObject的JsonSerializer參數了。通過重寫serializer方法,來具體指定某一列的數據類型,從而達到效果。
public class CustomDataTableConverter : DataTableConverter
{
private static void CreateRow(JsonReader reader, DataTable dt)
{
DataRow row = dt.NewRow();
reader.Read();
while(reader.TokenType == JsonToken.PropertyName)
{
string columnName = (string)reader.Value;
reader.Read();
DataColumn column = dt.Columns[columnName];
if(column == null)
{
Type columnDataType = GetColumnDataType(reader);
column = new DataColumn(columnName, columnDataType);
dt.Columns.Add(column);
}
if(column.DataType == typeof(DataTable))
{
if(reader.TokenType == JsonToken.StartArray)
{
reader.Read();
}
DataTable table = new DataTable();
while(reader.TokenType != JsonToken.EndArray)
{
CreateRow(reader, table);
reader.Read();
}
row[columnName] = table;
}
else if(column.DataType.IsArray && (column.DataType != typeof(byte[])))
{
if(reader.TokenType == JsonToken.StartArray)
{
reader.Read();
}
List<object> list = new List<object>();
while(reader.TokenType != JsonToken.EndArray)
{
list.Add(reader.Value);
reader.Read();
}
Array destinationArray = Array.CreateInstance(column.DataType.GetElementType(), list.Count);
Array.Copy(list.ToArray(), destinationArray, list.Count);
row[columnName] = destinationArray;
}
else
{
object val = DBNull.Value;
if(reader.Value != null)
{
if(column.DataType == typeof(string))
{
val = reader.Value.ToString();
}
else
{
if(string.IsNullOrEmpty(reader.Value.ToString()))
{
val = DBNull.Value;
}
else
{
val = Convert.ChangeType(reader.Value, column.DataType);
}
}
row[columnName] = val;
}
reader.Read();
}
row.EndEdit();
dt.Rows.Add(row);
}
}
private static Type GetColumnDataType(JsonReader reader)
{
JsonToken tokenType = reader.TokenType;
switch(tokenType)
{
case JsonToken.StartArray:
reader.Read();
if(reader.TokenType != JsonToken.StartObject)
{
return GetColumnDataType(reader).MakeArrayType();
}
return typeof(DataTable);
//這個地方需要對數值類型做特殊處理。如果能確認是數值列,那么可以用double類型替代,防止精度丟失。如果是字符串類型列,那么使用string類型,防止數據轉化出錯
case JsonToken.Integer:
return typeof(double);
case JsonToken.Float:
case JsonToken.String:
case JsonToken.Boolean:
case JsonToken.Date:
case JsonToken.Bytes:
return reader.ValueType;
case JsonToken.Null:
case JsonToken.Undefined:
return typeof(string);
}
throw new JsonException($"Unexpected JSON token while reading DataTable: {tokenType}");
}
private static void MakeTableSchema(string schema, DataTable dt)
{
if(!string.IsNullOrEmpty(schema) && dt != null)
{
string[] colinfos = schema.Trim(',').Split(',');
foreach(string colinfo in colinfos)
{
string[] info = colinfo.split('|');
if(!dt.Columns.Contains(info[0]))
{
dt.Columns.Add(info[0], Type.GetType("System." + info[i]));
}
}
}
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
DataTable dt = existingValue as DataTable;
if(dt == null)
{
dt = (objectType == typeof(DataTable)) ? new DataTable() : ((DataTable)Activator.CreateInstance(objectType));
}
if(reader.TokenType == JsonToken.PropertyName)
{
dt.TableName = (string)reader.Value;
reader.Read();
}
if(reader.TokenType == JsonToken.StartObject)
{
reader.Read();
while(reader.TokenType == JsonToken.PropertyName)
{
if((string)reader.Value == "SCHEMA")
{
reader.Read();
MakeTableSchema((string)reader.Value, dt);
reader.Read();
}
if((string)reader.Value == "TABLENAME")
{
reader.Read();
dt.TableName = (string)reader.Value;
reader.Read();
}
else if((string)reader.Value == "DATA")
{
reader.Read();
if(reader.TokenType == JsonToken.StartArray)
{
reader.Read();
}
while(reader.TokenType != JsonToken.EndArray)
{
CreateRow(reader, dt);
reader.Read();
}
}
}
reader.Read();
}
else if(reader.TokenType == JsonToken.StartArray)
{
reader.Read();
while(reader.TokenType != JsonToken.EndArray)
{
Create(reader, dt);
reader.Read();
}
}
return dt;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
DataTable table = (DataTable)value;
DefaultContractResolver contractResolver = serializer.ContractResolver as DefaultContractResolver;
StringBuilder sb = new StringBuilder();
foreach(DataColumns column in table.Columns)
{
sb.Append(column.ColumnName + "|" + column.DataType.Name + ",");
}
writer.WriteStartObject();
writer.WritePropertyName("SCHEMA");
serializer.Serializer(writer, sb.ToString());
writer.WritePropertyName("TABLENAME");
serializer.Serialize(writer, table.TableName);
writer.WritePropertyName("DATA");
writer.WriteStartArray();
foreach(DataRow row in table.Rows)
{
writer.WriteStartObject();
foreach(DataColumn column in row.Table.Columns)
{
if((serializer.NullValueHandling != NullValueHanding.Ignore) || ((row[column] != null) && (row[column] != DBNull.Value)))
{
writer.WritePropertyName((contractResolver != null) ? contractResolver.GetResolvedPropertyName(column.ColumnName) : column.ColumnName);
serializer.Serialize(writer, row[column]);
}
}
writer.WriteEndObject();
}
writer.WriteEndArray();
writer.WriteEndObject();
}
}
調用方法
JsonSerializer serializer = new JsonSerializer(); serializer.Converts.Add(new CustomDataTableConverter()); JObject.GetValue("[Key]").ToObject<DataTable>(serializer);
這樣就能相對正確地把數據轉成DataTable。