Блоґ

Home / Думки, нариси, різні випадки

Десеріалізація рядка у JSON

Виклики перетворення простого рядка в JSON

вівторок, 28 лютого 2023 р.

Deserialization

Comments

Деякий час тому я отримав задачу, яка на перший погляд здавалася доволі нескладною. У кількох словах:

  • є таблиця в базі даних.
  • одна колонка містить назву конфігураційної секції.
  • інша колонка містить рядок, який являє собою об'єкт JSON, який використовується для конфігурування деяких зовнішніх сутностей.
  • дані в цю таблицю вносяться вручну або через інші застосунки, але наш АПІ має іх читати. Таким чином, задача полягає в тому, аби вибрати записи з певним ід проєкта (це ще одна колонка), скласти  з них JSON об'єкт та видати його як вміст типу 'application/json' у відповіді АПІ. Структура видачы маэ бути десь такою:
    [
      "sectionName1" : {
        -- JSON value 1 from db table --
      },
      "sectionName2" : {
        -- JSON value 2 from db table --
      }
      ... and so on
    ]

Титульний малюнок для цієї статті показує приклад дійсних даних та їх видачу як результат цієї задачі.

На початках задача не мала вигляд чогось аж занадто складного. Але, розпочавши над нею роботу, я зіткнувся з чудернацькими обставинами, пов'язаними з екрануванням дотнетом символу подвійних лапок у рядку. Проста на перший позір ідея щодо того, як зробити таке перетворення, була очевидною — прочитати записи в Dictionary<string, string>, а відтак АПІ нехай сам видає їх назовні. Але результат виявився трохи негожим:


{
  "areaConfiguration": "{\"enabledAreas\": [\"stakeholders\",\"engagement\",\"reports\",\"land_referencing\",\"land_access\"]}",
  "programmeMapping": "{\"layerToProgramme\": {\"75612\": 1,\"75613\": 2,\"75614\": 3}}"
}

І це є цілком зрозумілим, оскільки json-десеріалайзер не має жодного уявлення про значення, окрім того, що то є рядки.

Наступною спробою вирішення задачі було замінити подвійні лапки одинарними. Але цей підхід також не спрацював:


{
  "areaConfiguration": "{'enabledAreas': ['stakeholders','engagement','reports','land_referencing','land_access']}",
  "programmeMapping": "{'layerToProgramme': {'75612': 1,'75613': 2,'75614': 3}}"
}

Отже, постала проблема — як десеріалізувати рядок з json-вмістом в деякий c#-об'єкт, аби під час видачі серіалайзер мав змогу створити правильний відгук АПІ? Всі рекомендації, що я знайшов їх у вебі, зводилися до десеріалізації суворо типізованих обє'ктів, але в моєму випадку формат об'єкту в початковому рядку заздалегідь невідомий. Цей об'єкт навіть може не бути пласким, а його властивості можуть мати значення будь-якого типу — число, булевське значення чи рядок. На думку спадало два варіанти, які варто було випробувати: анонімні об'єкти чи динаміки. Я розпочав саме з останніх і поступово здобувся на гідне рішення.

Яким чином воно працює в моєму випадку? Потрібні три кроки:

Крок 1 (репозиторій) — читання з бд в колекцію суворо типізованих об'єктів:


public async Task<RepositoryResultData<IEnumerable<GetSettings>>> GetAllSettingsAsync()
{
	const string sql = "SELECT Name, Configuration FROM dbo.ProjectSetting";

	using var dataAccessor = await _dataAccessorFactory.CreateDataAccessorAsync();

	var configuration = await dataAccessor.QueryAsync<GetSettings>(sql);

	return RepositoryResultData<IEnumerable<GetSettings>>.Succeed(configuration);
}

public class GetSettings
{
	public string Name { get; set; }

	public string Configuration { get; set; }
}

Крок 2 (сервіс) — деякі рядкові маніпуляції з кожним записом з тіїє колекції:


public async Task<ServiceResultResponse<string>> GetAllSettingsAsync()
{
	var result = await _settingsRepository.GetAllSettingsAsync();

	var join = string.Join(", ", result.Data.Select(x => $"\"{x.Name}\": {x.Configuration}"));

	return ServiceResultResponse<string>.Succeed("{" + join + "}");
}

Крок 3 (контролер) — десеріалізація у змінну типу dynamic і виробіток з ней ContentResult:


[HttpGet]
[Produces("application/json")]
public async Task<IActionResult> GetSettings()
{
	var result = await _settingsService.GetAllSettingsAsync();

	var d = JsonConvert.DeserializeObject<dynamic>(result.Response);

	return new ContentResult { Content = d.ToString(), ContentType = "application/json; charset=utf-8", StatusCode = 200 };
}

І це спрацювало!

Чи був у вас досвід вирішення подібної задачі? Цікаво, чи можна це зробити іншим чином?

Дякую за увагу!

© theyur.dev. All Rights Reserved. Designed by HTML Codex