using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Collections.ObjectModel;
using System.Runtime.Serialization;
using System.ComponentModel;
using System.Globalization;
using System.Runtime.Serialization.Formatters.Binary;
using System.Collections.Specialized;
using System.Web.Routing;
using System.Runtime.InteropServices;
using System.Text.RegularExpressions;
using System.Runtime.Serialization.Formatters;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json.Converters;
using Newtonsoft.Json.Serialization;
public class TypeInferringDataTableConverter : Newtonsoft.Json.Converters.DataTableConverter
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
if (reader.TokenType == JsonToken.Null)
DataTable dt = existingValue as DataTable;
dt = (objectType == typeof(DataTable))
: (DataTable)Activator.CreateInstance(objectType);
if (reader.TokenType == JsonToken.PropertyName)
dt.TableName = (string)reader.Value;
if (reader.TokenType == JsonToken.Null)
if (reader.TokenType != JsonToken.StartArray)
throw JsonSerializationExceptionHelper.Create(reader, "Unexpected JSON token when reading DataTable. Expected StartArray, got {0}.".FormatWith(CultureInfo.InvariantCulture, reader.TokenType));
var ambiguousColumnTypes = new HashSet<string>();
while (reader.TokenType != JsonToken.EndArray)
CreateRow(reader, dt, serializer, ambiguousColumnTypes);
private static void CreateRow(JsonReader reader, DataTable dt, JsonSerializer serializer, HashSet<string> ambiguousColumnTypes)
DataRow dr = dt.NewRow();
while (reader.TokenType == JsonToken.PropertyName)
string columnName = (string)reader.Value;
DataColumn column = dt.Columns[columnName];
Type columnType = GetColumnDataType(reader, out isAmbiguousType);
column = new DataColumn(columnName, columnType);
ambiguousColumnTypes.Add(columnName);
else if (ambiguousColumnTypes.Contains(columnName))
Type newColumnType = GetColumnDataType(reader, out isAmbiguousType);
ambiguousColumnTypes.Remove(columnName);
if (newColumnType != column.DataType)
column = ReplaceColumn(dt, column, newColumnType, serializer);
if (column.DataType == typeof(DataTable))
if (reader.TokenType == JsonToken.StartArray)
DataTable nestedDt = new DataTable();
var nestedUnknownColumnTypes = new HashSet<string>();
while (reader.TokenType != JsonToken.EndArray)
CreateRow(reader, nestedDt, serializer, nestedUnknownColumnTypes);
dr[columnName] = nestedDt;
else if (column.DataType.IsArray && column.DataType != typeof(byte[]))
if (reader.TokenType == JsonToken.StartArray)
List<object> o = new List<object>();
while (reader.TokenType != JsonToken.EndArray)
Array destinationArray = Array.CreateInstance(column.DataType.GetElementType(), o.Count);
Array.Copy(o.ToArray(), destinationArray, o.Count);
dr[columnName] = destinationArray;
dr[columnName] = (reader.Value != null) ? serializer.Deserialize(reader, column.DataType) : DBNull.Value;
static object RemapValue(object oldValue, Type newType, JsonSerializer serializer)
if (oldValue == DBNull.Value)
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];
private static Type GetColumnDataType(JsonReader reader, out bool isAmbiguous)
JsonToken tokenType = reader.TokenType;
case JsonToken.Undefined:
case JsonToken.StartArray:
if (reader.TokenType == JsonToken.StartObject)
return typeof(DataTable);
Type arrayType = GetColumnDataType(reader, out innerAmbiguous);
return arrayType.MakeArrayType();
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)
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 += 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);
return new JsonSerializationException(message);
internal static class StringUtils
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)
throw new ArgumentNullException("reader");
throw JsonSerializationExceptionHelper.Create(reader, "Unexpected end when reading JSON.");
static void Test<TDataTable>(string jsonTable, string columnName, Type expectedType) where TDataTable : DataTable
ConsoleAndDebug.WriteLine("JSON Sample: ");
ConsoleAndDebug.WriteLine(jsonTable);
var settings = new JsonSerializerSettings { Converters = new[] { new TypeInferringDataTableConverter() } };
var table = Newtonsoft.Json.JsonConvert.DeserializeObject<TDataTable>(jsonTable, settings);
ConsoleAndDebug.WriteLine(string.Format("{0} column type: {1}", columnName, table.Columns[columnName].DataType));
var newJsonTable = JsonConvert.SerializeObject(table, Newtonsoft.Json.Formatting.Indented, settings);
ConsoleAndDebug.WriteLine("Re-serialized JSON: ");
ConsoleAndDebug.WriteLine(newJsonTable);
if (table.Columns[columnName].DataType != expectedType)
throw new InvalidOperationException(string.Format("table.Columns[\"{0}\"].DataType != typeof({1})", columnName, expectedType));
if (!JToken.DeepEquals(JToken.Parse(jsonTable), JToken.Parse(newJsonTable)))
throw new InvalidOperationException("!JToken.DeepEquals(JToken.Parse(jsonTable), JToken.Parse(newJsonTable))");
ConsoleAndDebug.WriteLine("Old and new JSON are identical.");
public static void Test()
string jsonTable1 = "[{\"ShipmentDate\":null,\"Count\":2},{\"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\":null,\"Count\":2},{\"ShipmentDate\":\"2015-05-26T00:00:00\",\"Count\":1},{\"ShipmentDate\":\"2015-05-28T00:00:00\",\"Count\":2}]";
ConsoleAndDebug.WriteLine("");
Test<DataTable>(jsonTable1, "ShipmentDate", typeof(DateTime));
ConsoleAndDebug.WriteLine("");
Test<DataTable>(jsonTable2, "ShipmentDate", typeof(DateTime));
public static void Main()
ConsoleAndDebug.WriteLine("Json.NET version: " + typeof(JsonSerializer).Assembly.FullName);
ConsoleAndDebug.WriteLine("");
public static class ConsoleAndDebug
public static void Write(object s)
public static void WriteLine(object s)