Verification: a143cc29221c9be0

Php cast object to object

Пространства имен

Пространство имен System.Text.Json содержит все точки входа и основные типы. Пространство имен System.Text.Json.Serialization содержит атрибуты и интерфейсы API для сложных сценариев и настройки, характерной для сериализации и десериализации. В примерах кода, приведенных в этой статье, для одного или обоих пространств имен необходимо добавить директивы using:

using System.Text.Json;
using System.Text.Json.Serialization;

Как записать объекты .NET в формате JSON (сериализация)

Чтобы записать JSON в строку или в файл, вызовите метод JsonSerializer.Serialize.

В следующем примере показано создание JSON в виде строки:

string jsonString = JsonSerializer.Serialize(weatherForecast);

В примере ниже для создания JSON-файла используется синхронный код:

jsonString = JsonSerializer.Serialize(weatherForecast);
File.WriteAllText(fileName, jsonString);

В следующем примере для создания JSON-файла используется асинхронный код:

using FileStream createStream = File.Create(fileName);
await JsonSerializer.SerializeAsync(createStream, weatherForecast);

В предыдущих примерах для сериализуемого типа используется определение типа. Перегрузка Serialize() принимает параметр универсального типа:

jsonString = JsonSerializer.Serialize(weatherForecast);

Пример сериализации

Ниже приведен пример класса, который содержит свойства типа коллекции и определяемый пользователем тип:

public class WeatherForecastWithPOCOs
{
    public DateTimeOffset Date { get; set; }
    public int TemperatureCelsius { get; set; }
    public string Summary { get; set; }
    public string SummaryField;
    public IList DatesAvailable { get; set; }
    public Dictionary TemperatureRanges { get; set; }
    public string[] SummaryWords { get; set; }
}

public class HighLowTemps
{
    public int High { get; set; }
    public int Low { get; set; }
}

Совет

POCO означает традиционный объект среды CLR. POCO — это тип .NET, который не зависит от каких-либо типов платформы, например посредством наследования или атрибутов.

Выходные данные JSON из сериализации экземпляра предыдущего типа выглядят следующим образом. По умолчанию выходные данные JSON сокращены (удалены пробелы, отступы и символы новой строки):

{"Date":"2019-08-01T00:00:00-07:00","TemperatureCelsius":25,"Summary":"Hot","DatesAvailable":["2019-08-01T00:00:00-07:00","2019-08-02T00:00:00-07:00"],"TemperatureRanges":{"Cold":{"High":20,"Low":-10},"Hot":{"High":60,"Low":20}},"SummaryWords":["Cool","Windy","Humid"]}

В следующем примере показан тот же объект JSON, но с форматированием (т. е. структурированный с пробелами и отступами):

{
  "Date": "2019-08-01T00:00:00-07:00",
  "TemperatureCelsius": 25,
  "Summary": "Hot",
  "DatesAvailable": [
    "2019-08-01T00:00:00-07:00",
    "2019-08-02T00:00:00-07:00"
  ],
  "TemperatureRanges": {
    "Cold": {
      "High": 20,
      "Low": -10
    },
    "Hot": {
      "High": 60,
      "Low": 20
    }
  },
  "SummaryWords": [
    "Cool",
    "Windy",
    "Humid"
  ]
}

Сериализация в UTF-8

Чтобы выполнить сериализацию в UTF-8, вызовите метод JsonSerializer.SerializeToUtf8Bytes:

byte[] jsonUtf8Bytes =JsonSerializer.SerializeToUtf8Bytes(weatherForecast);

Также доступна перегрузка Serialize, которая принимает Utf8JsonWriter.

Сериализация в UTF-8 происходит примерно на 5-10 % быстрее, чем при использовании строковых методов. Разница заключается в том, что байты (например, UTF-8) не нужно преобразовывать в строки (UTF-16).

Поведение сериализации

К поддерживаемым типам относятся:

Для обработки дополнительных типов или для обеспечения функциональности, которая не поддерживается встроенными преобразователями, можно реализовать пользовательские преобразователи.

Как считать JSON как объекты .NET (десериализация)

Чтобы выполнить десериализацию из строки или файла, вызовите метод JsonSerializer.Deserialize.

В следующем примере показано считывание JSON из строки и создание экземпляра класса WeatherForecastWithPOCOs, показанного ранее для примера сериализации:

weatherForecast = JsonSerializer.Deserialize(jsonString);

Чтобы выполнить десериализацию из файла с помощью синхронного кода, выполните считывание файла в строку, как показано в следующем примере:

jsonString = File.ReadAllText(fileName);
weatherForecast = JsonSerializer.Deserialize(jsonString);

Чтобы выполнить десериализацию из файла с помощью асинхронного кода, вызовите метод DeserializeAsync:

using FileStream openStream = File.OpenRead(fileName);
weatherForecast = await JsonSerializer.DeserializeAsync(openStream);

Десериализация из UTF-8

Для десериализации из UTF-8 вызовите перегрузку JsonSerializer.Deserialize, которая принимает значения ReadOnlySpan или Utf8JsonReader, как показано в следующих примерах. В примерах предполагается, что JSON находится в массиве байтов jsonUtf8Bytes.

var readOnlySpan = new ReadOnlySpan(jsonUtf8Bytes);
WeatherForecast deserializedWeatherForecast = 
    JsonSerializer.Deserialize(readOnlySpan);
var utf8Reader = new Utf8JsonReader(jsonUtf8Bytes);
WeatherForecast deserializedWeatherForecast = 
    JsonSerializer.Deserialize(ref utf8Reader);

Поведение десериализации

При десериализации JSON применяются следующие правила поведения:

Для обеспечения функциональности, которая не поддерживается встроенными преобразователями, можно реализовать пользовательские преобразователи.

Сериализация в форматированный JSON

Чтобы структурировать выходные данные JSON, задайте для JsonSerializerOptions.WriteIndented значение true.

var options = new JsonSerializerOptions
{
    WriteIndented = true,
};
jsonString = JsonSerializer.Serialize(weatherForecast, options);

Ниже приведен пример типа для сериализации и структурирования данных JSON:

public class WeatherForecast
{
    public DateTimeOffset Date { get; set; }
    public int TemperatureCelsius { get; set; }
    public string Summary { get; set; }
}
{
  "Date": "2019-08-01T00:00:00-07:00",
  "TemperatureCelsius": 25,
  "Summary": "Hot"
}

При многократном использовании JsonSerializerOptions с одинаковыми параметрами не создавайте новый экземпляр JsonSerializerOptions при каждом использовании. Повторно используйте один и тот же экземпляр для каждого вызова. Дополнительные сведения см. в разделе Повторное использование экземпляров JsonSerializerOptions.

Включение полей

Используйте глобальный параметр JsonSerializerOptions.IncludeFields или атрибут [JsonInclude] для включения поля при сериализации или десериализации, как показано ниже:

using System;
using System.Text.Json;
using System.Text.Json.Serialization;

namespace Fields
{
    public class Forecast
    {
        public DateTime Date;
        public int TemperatureC;
        public string Summary;
    }

    public class Forecast2
    {
        [JsonInclude]
        public DateTime Date;
        [JsonInclude]
        public int TemperatureC;
        [JsonInclude]
        public string Summary;
    }

    public class Program
    {
        public static void Main()
        {
            var json =
                @"{""Date"":""2020-09-06T11:31:01.923395"",""TemperatureC"":-1,""Summary"":""Cold""} ";
            Console.WriteLine($"Input JSON: {json}");

            var options = new JsonSerializerOptions
            {
                IncludeFields = true,
            };
            var forecast = JsonSerializer.Deserialize(json, options);

            Console.WriteLine($"forecast.Date: {forecast.Date}");
            Console.WriteLine($"forecast.TemperatureC: {forecast.TemperatureC}");
            Console.WriteLine($"forecast.Summary: {forecast.Summary}");

            var roundTrippedJson =
                JsonSerializer.Serialize(forecast, options);

            Console.WriteLine($"Output JSON: {roundTrippedJson}");

            var forecast2 = JsonSerializer.Deserialize(json);

            Console.WriteLine($"forecast2.Date: {forecast2.Date}");
            Console.WriteLine($"forecast2.TemperatureC: {forecast2.TemperatureC}");
            Console.WriteLine($"forecast2.Summary: {forecast2.Summary}");

            roundTrippedJson = JsonSerializer.Serialize(forecast2);
            
            Console.WriteLine($"Output JSON: {roundTrippedJson}");
        }
    }
}

// Produces output like the following example:
//
//Input JSON: { "Date":"2020-09-06T11:31:01.923395","TemperatureC":-1,"Summary":"Cold"}
//forecast.Date: 9/6/2020 11:31:01 AM
//forecast.TemperatureC: -1
//forecast.Summary: Cold
//Output JSON: { "Date":"2020-09-06T11:31:01.923395","TemperatureC":-1,"Summary":"Cold"}
//forecast2.Date: 9/6/2020 11:31:01 AM
//forecast2.TemperatureC: -1
//forecast2.Summary: Cold
//Output JSON: { "Date":"2020-09-06T11:31:01.923395","TemperatureC":-1,"Summary":"Cold"}

Чтобы пропустить поля, предназначенные только для чтения, используйте глобальный параметр JsonSerializerOptions.IgnoreReadOnlyFields.

Методы расширения HttpClient и HttpContent

Сериализация и десериализация полезных данных JSON из сети являются обычными операциями. Методы расширения в HttpClient и HttpContent позволяют выполнять эти операции в одной строке кода. Эти методы расширения используют стандартные параметры веб-приложений для JsonSerializerOptions.

В следующем примере демонстрируется применение HttpClientJsonExtensions.GetFromJsonAsync и HttpClientJsonExtensions.PostAsJsonAsync:

using System;
using System.Net.Http;
using System.Net.Http.Json;
using System.Threading.Tasks;

namespace HttpClientExtensionMethods
{
    public class User
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public string Username { get; set; }
        public string Email { get; set; }
    }

    public class Program
    {
        public static async Task Main()
        {
            using HttpClient client = new()
            {
                BaseAddress = new Uri("https://jsonplaceholder.typicode.com")
            };

            // Get the user information.
            User user = await client.GetFromJsonAsync("users/1");
            Console.WriteLine($"Id: {user.Id}");
            Console.WriteLine($"Name: {user.Name}");
            Console.WriteLine($"Username: {user.Username}");
            Console.WriteLine($"Email: {user.Email}");

            // Post a new user.
            HttpResponseMessage response = await client.PostAsJsonAsync("users", user);
            Console.WriteLine(
                $"{(response.IsSuccessStatusCode ? "Success" : "Error")} - {response.StatusCode}");
        }
    }
}

// Produces output like the following example but with different names:
//
//Id: 1
//Name: Tyler King
//Username: Tyler
//Email: Tyler @contoso.com
//Success - Created

Для System.Text.Json существуют также методы расширения на HttpContent.

Методы расширения на HttpClient и HttpContent недоступны в System.Text.Json для .NET Core 3.1.

Структура данных

Во-первых, в коде должна быть отражена сама структура данных. Лучше всего это делать с использованием классов (аннотации JSDoc помогают ориентироваться в типах данных):

class ConfigEmailAuth {
    /** @type {string} */
    pass;
    /** @type {string} */
    user;
}

Это пример простой структуры данных, где каждый атрибут является примитивом. Если некоторые атрибуты сами являются структурами, то класс выглядит примерно так:

class ConfigEmail {
    /** @type {ConfigEmailAuth} */
    auth;
    /** @type {string} */
    from;
    /** @type {string} */
    host;
    /** @type {number} */
    port;
    /** @type {boolean} */
    secure;
}

Создание объектов

Как правило, создание экземпляра DTO в половине случаев связано разбором имеющейся структуры данных, полученной "по проводам" с "другой стороны". Поэтому конструктор DTO получает на вход некоторый JS-объект, из которого пытается извлечь знакомые ему данные:

/**
 * @param {ConfigEmailAuth|null} data
 */
constructor(data = null) {
    this.pass = data?.pass;
    this.user = data?.user;
}

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

/**
 * @param {ConfigEmail} data
 */
constructor(data = null) {
    this.auth = (data?.auth instanceof ConfigEmailAuth)
        ? data.auth : new ConfigEmailAuth(data?.auth);
    this.from = data?.from || 'default@from.com';
    this.host = data?.host || 'localhost';
    this.port = data?.port || 465;
    this.secure = data?.secure || true;
}

Если какой-то атрибут представляет из себя массив, то в конструкторе его разбор выглядит примерно так:

class ConfigItems {
    /** @type {Item[]} */
    items;

    /**
     * @param {ConfigItems} data
     */
    constructor(data = null) {
        this.items = Array.isArray(data?.items)
            ? data.items.map((one) => (one instanceof Item) ? one : new Item(one))
            : [];
    }
}

Если какие-то данные должны быть сохранены в атрибуте без разбора, то это тоже возможно (хотя к DTO имеет такое себе отношение):

class SomeDto {
    /** @type {Object} */
    unknownStruct;

    /**
     * @param {SomeDto} data
     */
    constructor(data = null) {
        this.unknownStruct = data?.unknownStruct;
    }

}

Метаданные

Метаданные - это информация о коде. Метаданные позволяют отследить, где используются соответствующие атрибуты объекта:

class SaleOrder {
    /** @type {number} */
    amount;
    /** @type {number} */
    id;
}

SaleOrder.AMOUNT = 'amount';
SaleOrder.ID = 'id';

Например, при выборке данных из БД:

const query = trx.from('sale');
query.select([
    {[SaleOrder.ID]: 'saleId'},
    {[SaleOrder.AMOUNT]: 'totalAmount'},
    // ...
]);

Результирующую выборку можно напрямую передавать в конструктор SaleOrder, а затем получившийся DTO выкидывать на web в качестве ответа.

Резюме

Если сводить воедино все три составляющих DTO (структура, конструктор, метаданные), то получается примерно такой es-модуль:

import ConfigEmailAuth from './ConfigEmailAuth.mjs';

export default class ConfigEmail {
    /** @type {ConfigEmailAuth} */
    auth;
    /** @type {string} */
    from;
    // ...

    /**
     * @param {ConfigEmail} data
     */
    constructor(data = null) {
        this.auth = (data?.auth instanceof ConfigEmailAuth)
            ? data.auth : new ConfigEmailAuth(data?.auth);
        this.from = data?.from || 'default@from.com';
        // ...
    }

}

ConfigEmail.AUTH = 'auth';
ConfigEmail.FROM = 'from';
// ...

Подобный подход позволяет извлекать знакомые подструктуры данных из больших структур (конфигурационных файлов, ответов от сервисов и т.п.), непосредственно относящиеся к текущему контексту, а IDE, за счёт аннотаций, имеет возможность помогать разработчику ориентироваться в этой подструктуре.

"Вот и всё, что я могу сказать об этом." (с)