我们用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。