Токен PropertyName в состоянии Property приведет к неверному объекту JSON.при использовании кастомного JsonConverter <T> - PullRequest
0 голосов
/ 30 января 2019

Я пытаюсь сериализовать / десериализовать .NET DataSet, используя Json.NET и пользовательский сериализатор.Я знаю, что многие из вас скажут мне не делать этого (я видел это на других постах). У меня есть веская причина и я хочу продолжить этот путь.

Моя сериализация основана на том факте, что .NET DataSetможет экспортировать свою схему и данные в XML, а затем повторно импортировать то же самое;Исходя из этого, я пытаюсь создать конвертер, который позволит мне захватывать этот XML, конвертировать его в JSON, затем конвертировать обратно и перезагружать.Моя реализация выглядит следующим образом ...

class DataSetConverter : JsonConverter<DataSet>
{
    public override DataSet ReadJson(JsonReader reader, Type objectType, DataSet existingValue, bool hasExistingValue, JsonSerializer serializer)
    {
        DataSet dataSet = new DataSet();
        JObject jObject = JObject.Load(reader);

        String json = jObject.ToString();
        XDocument document = JsonConvert.DeserializeXNode(json);
        using (MemoryStream memoryStream = new MemoryStream())
        using (StreamWriter streamWriter = new StreamWriter(memoryStream))
        {
            streamWriter.Write(document.ToString(SaveOptions.None));
            streamWriter.Flush();

            memoryStream.Position = 0;
            dataSet.ReadXml(memoryStream);
        }

        return dataSet;
    }

    public override void WriteJson(JsonWriter writer, DataSet dataSet, JsonSerializer serializer)
    {
        using (MemoryStream memoryStream = new MemoryStream())
        {
            dataSet.WriteXml(memoryStream, XmlWriteMode.WriteSchema);
            using (StreamReader reader = new StreamReader(memoryStream))
            {
                memoryStream.Seek(0, SeekOrigin.Begin);
                XDocument document = XDocument.Parse(reader.ReadToEnd());
                writer.WriteRaw(JsonConvert.SerializeXNode(document, Formatting.Indented, false));
            }
        }
    }
}

Используется следующим образом (чисто сериализация объекта DataSet), он работает (мой новый DataSet имеет ту же схему и данные, что и оригинал) ...

DataSet originalInserts = new DataSet("Inserts");
DataTable originalStuff = originalInserts.Tables.Add("Stuff");

originalStuff.Columns.Add("C1", typeof(String));
originalStuff.Columns.Add("C2", typeof(Int64));
originalStuff.Columns.Add("C3", typeof(Guid));
originalStuff.Columns.Add("C4", typeof(float));

originalStuff.Rows.Add("One", 2, Guid.NewGuid(), 4.4);

String json = JsonConvert.SerializeObject(originalInserts, Formatting.Indented, new DataSetConverter());

DataSet newInsertsFromConvertedXml = (DataSet)JsonConvert.DeserializeObject(json, typeof(DataSet), new DataSetConverter());

Однако, если я тогда попытаюсь использовать тот же конвертер с объектом, который содержит DataSet (тот же DataSet, как указано выше) ...

public class TestClass
{
    public DataSet Inserts { get; set; }

    public String SomethingElse { get; set; }
}

TestClass testClass = new TestClass { Inserts = originalInserts, SomethingElse = "Me" };
json = JsonConvert.SerializeObject(testClass, Formatting.Indented, new DataSetConverter());

Сбой при

Token PropertyName в состоянии Property приведет к неверному объекту JSON.Путь ''.

Я также пытался украсить DataSet на TestClass атрибутом JsonConverter и удалить преобразователь из вызова метода Serialize, но получил тот же результат ...

public class TestClass
{
    [JsonConverter(typeof(DataSetConverter))]
    public DataSet Inserts { get; set; }

    public String SomethingElse { get; set; }
}

TestClass testClass = new TestClass { Inserts = originalInserts, SomethingElse = "Me" };
json = JsonConvert.SerializeObject(testClass, Formatting.Indented);

Чего мне не хватает?

1 Ответ

0 голосов
/ 01 февраля 2019

Ваша основная проблема заключается в том, что вы должны звонить WriteRawValue() вместо WriteRaw():

writer.WriteRawValue(JsonConvert.SerializeXNode(document, Formatting.Indented, false)); 

Документация для WriteRawValue() сообщает:

Записывает необработанный JSON, где ожидается значение, и обновляет состояние автора.

Принимая во внимание, что документация WriteRaw() заявляет:

Записываетсырой JSON без изменения состояния автора.

Неспособность продвинуть состояние автора объясняет, почему возникает исключение, когда делается попытка записи последующего содержимого.

При этом вы создаете много ненужных промежуточных string, Stream и JObject представлений внутри вашего конвертера.Более простой подход, в WriteJson() к:

  1. Создайте XDocument и напишите DataSet непосредственно в него, используя XContainer.CreateWriter();

  2. Сериализация XDocument непосредственно для входящего JsonWriter путем создания локального XmlNodeConverter.

Сериализация будет следовать обратному процессу.Таким образом, ваш DataSetConverter будет выглядеть следующим образом:

class DataSetConverter : JsonConverter<DataSet>
{
    public override DataSet ReadJson(JsonReader reader, Type objectType, DataSet existingValue, bool hasExistingValue, JsonSerializer serializer)
    {
        if (reader.MoveToContent().TokenType == JsonToken.Null)
            return null;
        var converter = new XmlNodeConverter { OmitRootObject = false };
        var document = (XDocument)converter.ReadJson(reader, typeof(XDocument), existingValue, serializer);
        using (var xmlReader = document.CreateReader())
        {
            var dataSet = existingValue ?? new DataSet();
            dataSet.ReadXml(xmlReader);
            return dataSet;
        }
    }

    public override void WriteJson(JsonWriter writer, DataSet dataSet, JsonSerializer serializer)
    {
        var document = new XDocument();
        using (var xmlWriter = document.CreateWriter())
        {
            dataSet.WriteXml(xmlWriter, XmlWriteMode.WriteSchema);
        }
        var converter = new XmlNodeConverter { OmitRootObject = false };
        converter.WriteJson(writer, document, serializer);
    }
}

public static partial class JsonExtensions
{
    public static JsonReader MoveToContent(this JsonReader reader)
    {
        // Start up the reader if not already reading, and skip comments
        if (reader.TokenType == JsonToken.None)
            reader.Read();
        while (reader.TokenType == JsonToken.Comment && reader.Read())
            {}
        return reader;
    }
}

Примечания:

  1. Вы наследуете от JsonConverter<DataSet> и ReadJson() Вы создаете объект типа DataSet напрямую.Однако, как показано в справочном источнике , JsonConverter<T>.CanConvert(Type objectType) применяется ко всем подклассам типа T также:

    public sealed override bool CanConvert(Type objectType)
    {
        return typeof(T).IsAssignableFrom(objectType);
    }
    

    Таким образом, вам может понадобитьсяпереопределить CanConvert и применить его только тогда, когда тип объекта равен typeof(DataSet) - но поскольку метод был запечатан, вы не можете.Следовательно, может оказаться необходимым наследовать от неуниверсального базового класса JsonConverter.

...