System.Text. Json - десериализовать вложенный объект как строку - PullRequest
1 голос
/ 25 февраля 2020

Я пытаюсь использовать System.Text.Json.JsonSerializer для частичной десериализации модели, поэтому одно из свойств читается как строка, содержащая оригинал JSON.

public class SomeModel
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Info { get; set; }
}

Пример кода

var json = @"{
                 ""Id"": 1,
                 ""Name"": ""Some Name"",
                 ""Info"": {
                     ""Additional"": ""Fields"",
                     ""Are"": ""Inside""
                 }
             }";

var model = JsonSerializer.Deserialize<SomeModel>(json);

должен генерировать модель, свойство Info которой содержит объект Info из исходного JSON в виде строки:

{
    "Additional": "Fields",
    "Are": "Inside"
}

Не работает из коробки и выбрасывает исключение:

System.Text. Json .JsonException: ---> System.InvalidOperationException: Невозможно получить значение типа токена 'StartObject' в виде строки.

Что я пробовал до сих пор:

public class InfoToStringConverter : JsonConverter<string>
{
    public override string Read(
        ref Utf8JsonReader reader, Type type, JsonSerializerOptions options)
    {
        return reader.GetString();
    }

    public override void Write(
        Utf8JsonWriter writer, string value, JsonSerializerOptions options)
    {
        throw new NotImplementedException();
    }
}

и примените его в модели как

[JsonConverter(typeof(InfoToStringConverter))]
public string Info { get; set; }

и добавьте опции к JsonSerializer

var options = new JsonSerializerOptions();
options.Converters.Add(new InfoToStringConverter());
var model = JsonSerializer.Deserialize<SomeModel>(json, options);

Тем не менее, он выдает то же исключение:

System.Text. Json .JsonException: ---> System.InvalidOperationException: Невозможно получить значение типа токена 'StartObject' как строка.

Какой правильный рецепт для приготовления того, что мне нужно? Он работал аналогичным образом, используя Newtonsoft.Json.

Обновление

Для меня важно сохранить вложенный объект JSON как можно более оригинальным. Поэтому я бы избегал таких вариантов, как десериализацию как Dictionary и сериализацию обратно, потому что я боюсь вносить нежелательные изменения.

Ответы [ 2 ]

2 голосов
/ 25 февраля 2020

Найден правильный способ, как правильно читать вложенный объект JSON внутри JsonConverter. Полное решение состоит в следующем:

public class SomeModel
{
    public int Id { get; set; }

    public string Name { get; set; }

    [JsonConverter(typeof(InfoToStringConverter))]
    public string Info { get; set; }
}

public class InfoToStringConverter : JsonConverter<string>
{
    public override string Read(
        ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        using (var jsonDoc = JsonDocument.ParseValue(ref reader))
        {
            return jsonDoc.RootElement.GetRawText();
        }
    }

    public override void Write(
        Utf8JsonWriter writer, string value, JsonSerializerOptions options)
    {
        throw new NotImplementedException();
    }
}

В самом коде нет необходимости даже создавать параметры:

var json = @"{
                 ""Id"": 1,
                 ""Name"": ""Some Name"",
                 ""Info"": {
                     ""Additional"": ""Fields"",
                     ""Are"": ""Inside""
                 }
             }";

var model = JsonSerializer.Deserialize<SomeModel>(json);

Необработанный текст JSON в свойстве Info содержит даже дополнительные пробелы, введенные в примере для хорошей читаемости.

И нет смешивания представления модели и ее сериализации, как отметил @PavelAnikhouski в своем ответе.

2 голосов
/ 25 февраля 2020

Вы можете использовать атрибут JsonExtensionData для этого и объявить свойство Dictionary<string, JsonElement> или Dictionary<string, object> в вашей модели для хранения этой информации

public class SomeModel
{
    public int Id { get; set; }
    public string Name { get; set; }

    [JsonExtensionData]
    public Dictionary<string, JsonElement> ExtensionData { get; set; }

    [JsonIgnore]
    public string Data
    {
        get
        {
            return ExtensionData?["Info"].GetRawText();
        }
    }
}

Затем вы можете добавить дополнительное свойство для получения строки из этого словаря по ключу Info. В приведенном выше коде свойство Data будет содержать ожидаемую строку

{
    "Additional": "Fields",
    "Are": "Inside"
}

. По некоторым причинам добавление свойства с тем же именем Info не работает, даже с JsonIgnore. Подробнее см. Обработка переполнения JSON.

Вы также можете объявить свойство Info как тип JsonElement и получить из него необработанный текст

public class SomeModel
{
    public int Id { get; set; }
    public string Name { get; set; }
    public JsonElement Info { get; set; }
}
var model = JsonSerializer.Deserialize<SomeModel>(json);
var rawString = model.Info.GetRawText();

Но это приведет к смешению представления модели и ее сериализации.

Другой вариант - анализировать данные, используя JsonDocument, перечислять свойства и анализировать их одно за другим, например

var document = JsonDocument.Parse(json);
foreach (var token in document.RootElement.EnumerateObject())
{
    if (token.Value.ValueKind == JsonValueKind.Number)
    {
        if(token.Value.TryGetInt32(out int number))
        {
        }
    }
    if (token.Value.ValueKind == JsonValueKind.String)
    {
        var stringValue = token.Value.GetString();
    }
    if(token.Value.ValueKind== JsonValueKind.Object)
    {
        var rawContent = token.Value.GetRawText();
    }
}
...