Основная проблема здесь в том, что DataTableConverter
Json.NET записывает каждый DataColumn.DataType
, просматривая значения токенов, присутствующие только в первой строке . Он работает таким образом, потому что он переводит JSON для таблицы, а не загружает целостность в промежуточную иерархию JToken
. Хотя потоковая передача обеспечивает лучшую производительность при уменьшенном использовании памяти, это означает, что значения null
в первой строке могут приводить к некорректно типизированным столбцам.
Это проблема, возникающая время от времени в stackoverflow, для экземпляр в вопросе deserialize datatable с отсутствующим первым столбцом . В этом случае вопросник заранее знал, что тип столбца должен быть double
. В вашем случае вы указали, что схема datatable является динамической , поэтому ответ не может быть использован. Однако, как и в случае с этим вопросом, поскольку Json.NET является открытым исходным кодом под лицензией MIT , можно создать модифицированную версию своего DataTableConverter
с необходимой логикой.
Как оказалось, можно корректно установить тип столбца, сохранив при этом поведение потоковой передачи, запомнив столбцы с неоднозначными типами данных, а затем заменив эти столбцы на правильно напечатанные столбцы, когда можно определить правильный тип:
///
/// Converts a to and from JSON.
///
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
///
/// Reads the JSON representation of the object.
///
/// The to read from.
/// Type of the object.
/// The existing value of object being read.
/// The calling serializer.
/// The object value.
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();
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 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();
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
Затем используйте его как:
var settings = new JsonSerializerSettings { Converters = new[] { new TypeInferringDataTableConverter() } };
DataTable tbl1 = Newtonsoft.Json.JsonConvert.DeserializeObject(jsonTable1, settings);
DataTable tbl2 = Newtonsoft.Json.JsonConvert.DeserializeObject(jsonTable2, settings);
Не устанавливайте NullValueHandling = NullValueHandling.Ignore
, поскольку нули теперь обрабатываются правильно.
Прототип fiddle
Обратите внимание, что, хотя этот класс обрабатывает повторную типизацию столбцов со значениями null
, он не обрабатывает повторную типизацию столбцов, содержащих значения массива, где первый элемент массива имеет значение null. Например, если первая строка некоторого столбца имеет значение
[null, null, null, 314, null]
Тогда тип столбца, который был бы выбран, в идеале был бы typeof( long? [] )
, однако это не выполняется здесь. Вероятно, необходимо было бы полностью загрузить JSON в иерархию JToken
, чтобы сделать это определение.
Я думаю, что у Вас должен быть хороший взгляд на Delphi 2009.
Delphi 2009 имеет изменения в поддержке COM, включая основанную на тексте замену для двоичных файлов TLB.
Можно читать больше на блоге Chris Bensen.
В удаленном прошлом (прежде чем я начал работать на CodeGear) я разочаровался в нечетном Delphi-ized язык IDL, что IDE, представленный, и, записал мой собственный IDL и скомпилировал его с помощью MS midl. Это в основном работало; единственная выгода, IIRC, удостоверялась, что dispids (идентификационный атрибут) были корректны в интерфейсах автоматизации (dispinterfaces) для методов считывания свойства и методов set - был некоторый инвариант, который ожидал tlibimp, но midl не гарантировал.
Однако теперь, когда Delphi 2009 использует безопасное подмножество midl синтаксиса и включает компилятор для этого midl в поле и интегрированный в IDE, этими проблемами должна быть вещь прошлого.
Мы также только что установили Delphi 2009, и это, действительно кажется, улучшило поддержку Typelibraries. Однако я работал с COM и библиотеками типов в течение достаточно долгого времени и здесь являюсь своими общими глюками, которые я нашел за эти годы. Я согласовал бы его симпатичный багги и являюсь полностью до Delphi 2006 (наша версия до использования 2009).
Однако я думаю, что Вашим лучшим решением является, вероятно, обновление. Вы получаете поддержку Unicode также.
Используя Delphi 2009 значительно вынул большую часть боли из огромных файлов TLB, и преобразование наших существующих объектов было безболезненным, но наши объекты com не пользуются никакими сторонними библиотеками.
Мы будем перемещать наши gui приложения по тому, после того как поставщики библиотеки выпускают поддерживаемые версии.
Тот же опыт с интерфейсом TLB здесь: мы просто прекратили использовать его.
Мы работаем с несколькими отдельными файлами IDL (ручная сборка) для различных частей нашей платформы, используя конструкцию #include для включения их в IDL реального приложения, затем генерируем единственный tlb использование MIDL и tlibimp это. Если приложение не имеет никакого IDL предварительная скомпилированная версия сама по себе другой платформы, файлы TLB доступны.
Каждый раз, когда платформа вводит новую версию, скрипт запущен, чтобы повторно создать ГУИДЫ во всех необходимых интерфейсах в файлах IDL.
Это служило нам хорошо много лет, и чтобы мы отодвинулись, новый набор инструментов IDL/TLB Delphi 2009 должен будет быть не только интегрирован в IDE, но также и универсальный когда дело доходит до автоматизированных сборок и этажерки. Не могу дождаться, чтобы пачкать руки с некоторыми экспериментами!