Inquirer EntertainmentGrace Lee reveals pregnancy with ‘miracle baby’ at 43Daily MaverickWHAT’S COOKING: A steak salad with baby potatoes, broccoli, green beans, tomatoes and two dressingsPunchDSS arraigns man over fake Tinubu letter to US PresidentThe Jerusalem PostChannel 14 reporter verbally assaulted, harassed while covering voting in HaifaBollywood HungamaSaif Ali Khan and Vidya Balan reunite for Ritesh Batra’s Long Years Ago; Mélanie Laurent joins castESPNTransfer rumors, news: Real Madrid eye Liverpool's Alexis Mac AllisterInquirerKidapawan Diocese mourns desecration of parish churchUOLSignos: confira seu horóscopo desta terça (27)ZDF heuteAktuelle Pressemitteilungen des ZDFKhaosod EnglishIsraelis vote in a tense national election seen as a referendum on NetanyahuBBC NewsTruce begins in Ukraine city where residents ate weeds to surviveObservador DesportoAs notícias das 9h
The Daily Newsstand · Free, Always
Tuesday, October 27, 2026

Валидация JSON во входящих REST-запросах DATAREON Platform

Translate

С вами снова Илья Вязников, инженер сопровождения СОФРОС. Продолжаю делиться практическими приёмами работы с платформой.

При интеграции информационных систем через REST API зачастую возникает необходимость проверки входящих данных на соответствие установленным требованиям. При этом недостаточно убедиться лишь в наличии обязательных полей: данные могут иметь некорректный тип, недопустимый формат или не соответствовать бизнес-правилам принимающей системы. 

Реализовывать одинаковые проверки отдельно в системах отправителя и получателя не всегда рационально, особенно в сложных интеграционных контурах с большим количеством участников. Более гибкий подход - вынести проверку на уровень интеграционной платформы. Это позволяет централизованно управлять правилами валидации, выявлять ошибки до передачи данных в целевые системы и сокращать необходимость доработок в прикладном ПО. 

Для ЛЛ Спойлер: что выяснилось на тестах

При тестировании кейса выяснилась интересная особенность: DATAREON Platform может отклонить запрос ещё до выполнения пользовательского кода валидации. В таком случае отправитель получит информацию об ошибках типов данных, но нарушения бизнес-правил будут обнаружены только после их исправления. 

Получается, что даже при едином формате ответа ошибки могут обнаруживаться последовательно. В статье покажем, как устроены оба уровня проверки, какие данные платформа преобразует автоматически и где возникают ограничения.

В этой статье на практическом примере рассмотрим, как организовать двухуровневую валидацию JSON в DATAREON Platform:

  1. Типы. Поля типа данных объявляем с настоящими типами (String, DateTime, Decimal, Int), платформа сама отклоняет значения не того типа.

  2. Бизнес-правила. Формат кода товара, диапазоны, обязательность - всё, что типом не выразить, проверяем в коде веб-обработчика.

Ошибки обоих уровней клиент получает в одном и том же JSON-формате благодаря настройке responseErrorSettings. Например:

{
  "errorCode": 400,
  "errorMessage": "данные не прошли проверку",
  "notValidFields": "meta.created_at,items[0].code"
}

Корректное сообщение получает HTTP 200 и уходит в Platform на дальнейшую обработку.

Все примеры ниже проверены на стенде DATAREON Platform 3.2.

Сценарий

Представим простой интеграционный сценарий: складская система принимает новую партию и передаёт в DATAREON Platform пакет данных о регистрации товара. Дальше эти данные уходят в учётную систему, где дата должна быть датой, цена - числом, а код товара - в формате справочника.

В meta содержатся сведения об источнике и документе, а в массиве items - передаваемые товарные позиции:

{
  "meta": {
    "source": "warehouse",
    "document_type": "product",
    "created_at": "2026-10-21"
  },
  "items": [
    {
      "name": "Товар 1",
      "code": "A-001",
      "price": 1500.00,
      "quantity": 10
    }
  ]
}

К данным предъявляем такие требования:

Поле

Тип

Правило

meta.source

Строка

Обязательно

meta.document_type

Строка

Обязательно

meta.created_at

Дата/время

Обязательно, не из будущего

items

Массив

Хотя бы одна позиция

items[].name

Строка

Обязательно

items[].code

Строка

Строго 5 символов вида A-001: латинская буква A–Z, дефис, три цифры

items[].price

Десятичное число

Больше 0

items[].quantity

Целое число

Больше 0

Если что-то не так, складская система должна сразу получить ответ со списком проблемных полей, а не узнавать об ошибке от сотрудников учёта через неделю.

Шаг 1. Тип данных: типизируем поля

Первая и самая дешёвая линия защиты — тип данных. Если объявить price, quantity и created_at как String, платформа примет в них что угодно, и проверять формат придётся вручную для каждого поля.
Если объявить их с настоящими типами, Datareon Platform сама сообщит что тип данных не корректный.

Создаём тип данных Habr_Example с двумя группами полей:

Группа

Поле

Тип в Platform

meta

source

String

meta

document_type

String

meta

created_at

DateTime

items (массив)

name

String

items (массив)

code

String

items (массив)

price

Decimal

items (массив)

quantity

Int

Структура в Platform

Структура в Platform

Что даёт типизация в коде обработчика: created_at приходит уже как DateTime, price - как decimal, quantity - как int. Никаких DateTime.TryParse и int.TryParse: можно сразу сравнивать даты и числа.

Одна тонкость, которую мы увидим на тестах. Если поле в JSON просто не передано, платформа не считает это ошибкой типа и подставляет значение по умолчанию:

Тип поля

Значение, если поле не передано

String

"" (пустая строка, не null)

DateTime

01.01.1900 00:00:00 (минимальный год из настроек поля)

Decimal, Int

0

Массив

Пустой список

А вот явный null в не-nullable поле платформа отклоняет сама.
Поэтому типизация не отменяет проверку обязательности - она лишь меняет её форму: вместо «строка пустая?» проверяем «дата не равна 1900 году?» и «количество больше нуля?».

Шаг 2. Внешняя система и responseErrorSettings

Нам нужна внешняя система типа Расширенный REST API. Именно в её конфигурации задаётся единый формат ответа об ошибке - блок responseErrorSettings.

По умолчанию его в конфигурации нет: переключаемся в Режим редактора конфигурации и добавляем вручную.

Переход в режим конфигурации внешней системы

Переход в режим конфигурации внешней системы

"responseErrorSettings": {
  "$type": "DT.ConfigurationRepository.Configuration.Adapter.ResponseErrorSettings, DT_ConfigurationRepository",
  "bodyJsonTemplate": "{\n  \"errorCode\": {errorCode},\n  \"errorMessage\": \"{errorMessage}\",\n  \"notValidFields\": {notValidFields}\"\n}",
  "validationErrorCode": 400
}
блок responseErrorSettings в конфигурации адаптера

блок responseErrorSettings в конфигурации адаптера

  • validationErrorCode - HTTP-код, который получит клиент при ошибке валидации. У нас 400.

  • bodyJsonTemplate - шаблон тела ответа. Это строка с экранированным JSON, а не вложенный объект. Платформа подставляет в неё плейсхолдеры {errorCode}, {errorMessage} и {notValidFields}; можно использовать любой их набор и свои имена ключей.

Текст ошибки и список полей в самих настройках не задаются: их заполняет тот, кто обнаружил ошибку, - платформа или код обработчика.

Когда срабатывает responseErrorSettings

Распространённое заблуждение — что единый JSON‑ответ формируется только при ValidateParametersException. На самом деле запрос считается невалидным и получает ответ по шаблону в четырёх случаях:

Случай

Кто формирует ошибку

Нарушены обязательные параметры: тип значения не соответствует ожидаемому или не передан обязательный параметр

Платформа, до запуска кода обработчика

Обработчик выбросил ValidateParametersException

Код обработчика

В ответе обработчика с флагом «Ожидание ответа» пришло свойство ResponseCode из диапазона 400–500

Процесс, который сформировал ответ

Возник WebServiceException

Платформа (внутренняя ошибка)

Кроме того, при настроенном responseErrorSettings в том же формате возвращаются ошибки авторизации и отправки на сервер. Иначе говоря, это настройка формата всех ошибочных ответов системы, а ValidateParametersException — лишь способ заявить об ошибке из своего кода.

В нашем кейсе работают первые два случая: типы проверяет платформа, бизнес‑правила — наш код обработчика.

После изменения конфигурации сохраняем и применяем изменения.

Шаг 3. Веб‑обработчик и проверка бизнес‑правил

Создаём веб‑обработчик HND_RestAPI_Habr_Example_ToPlatform и привязываем его к внешней системе:

  • Тип: Веб‑обработчик, направление В Platform

  • Метод: POST, шаблон пути: /habr/products

  • На вкладке В Platform — переменная ProductRegistration типа Habr_Example

Настройки обработчика

Настройки обработчика

Вкладка В Platform

Вкладка В Platform

Когда на вкладке «В Platform» указана типизированная переменная (Тип данных), платформа сама десериализует в неё тело запроса. Именно на этом шаге отсекаются «вчера» в дате и «многа» в числе, и код обработчика для таких запросов уже не вызывается.

После десериализации RunMessage.GetBodyAsString() возвращает пустую строку, в коде обработчика работать необходимо только через переменную, указанную на вкладке В Platform.

В код обработчика доходят только запросы с корректными типами. Здесь проверяем то, что типом не выразить: заполненность, формат кода и диапазоны. 

На вкладке Код:

const string DataTypeName = "Habr_Example";
var notValidFields = new NotValidFields();
var codeFormat = new System.Text.RegularExpressions.Regex(@"^[A-Z]-\d{3}$"); // A-001
var emptyDate = new DateTime(1900, 1, 1); // так выглядит незаполненная дата (minYear типа)


var doc = ProductRegistration;
if (doc == null)
{
    notValidFields.Add("body");
}
else
{
    // meta
    if (doc.meta == null)
    {
        notValidFields.Add("meta");
    }
    else
    {
        if (string.IsNullOrWhiteSpace(doc.meta.source))
            notValidFields.Add("meta.source");


        if (string.IsNullOrWhiteSpace(doc.meta.document_type))
            notValidFields.Add("meta.document_type");


        // поле не передано или дата из будущего (запас 1 день - на разницу часовых поясов)
        if (doc.meta.created_at <= emptyDate || doc.meta.created_at > DateTime.Now.AddDays(1))
            notValidFields.Add("meta.created_at");
    }


    // items
    if (doc.items == null || doc.items.Count == 0)
    {
        notValidFields.Add("items");
    }
    else
    {
        for (int i = 0; i < doc.items.Count; i++)
        {
            var item = doc.items[i];
            string prefix = $"items[{i}]";


            if (item == null)
            {
                notValidFields.Add(prefix);
                continue;
            }


            if (string.IsNullOrWhiteSpace(item.name))
                notValidFields.Add($"{prefix}.name");


            if (string.IsNullOrEmpty(item.code) || !codeFormat.IsMatch(item.code))
                notValidFields.Add($"{prefix}.code");


            if (item.price <= 0)
                notValidFields.Add($"{prefix}.price");


            if (item.quantity <= 0)
                notValidFields.Add($"{prefix}.quantity");
        }
    }
}


string title = doc?.meta == null
    ? "(неизвестно)"
    : $"{doc.meta.source}_{doc.meta.document_type}_{doc.meta.created_at:yyyy-MM-dd}";


if (notValidFields.Fields.Count > 0)
{
    Logger.Warning($"Название: \"{title}\", Тип данных: {DataTypeName}, не прошёл валидацию: {string.Join(", ", notValidFields.Fields)}");


    throw new ValidateParametersException(notValidFields, "данные не прошли проверку");
}


Logger.Info($"Название: \"{title}\", Тип данных: {DataTypeName}, прошёл валидацию, передан дальше");

Как это работает:

  1. NotValidFields - контейнер платформы для имён проблемных полей. Собираем все ошибки проверок, обнаруженные за один вызов обработчика, чтобы отправителю не пришлось исправлять их по одной. При этом ошибки типов обнаруживаются раньше, поэтому часть нарушений бизнес-правил станет видна только после исправления типов данных (подробно - в разборе «плохой» позиции ниже).

  2. Для массива указываем индекс (items[0].code) - так же, как это делает сама платформа при ошибках типов.

  3. Формат кода проверяем регулярным выражением ^[A-Z]-\d{3}$: ровно одна латинская заглавная буква, дефис и ровно три цифры. Кириллическая «Ж» или даже похожая на латинскую кириллическая «А» не пройдут.

  4. Для created_at оставляем запас в одни сутки: отправитель может работать в другом часовом поясе. Например, у склада во Владивостоке (UTC+10) уже наступило 22 октября, а на сервере в Москве ещё 21-е - без запаса его сегодняшний документ был бы отклонён как документ из будущего. Цена такого компромисса - платформа примет и дату завтрашнего дня. Если все отправители работают в одном часовом поясе с сервером, проверку можно сделать строже: doc.meta.created_at.Date > DateTime.Today.

  5. При ошибках выбрасываем ValidateParametersException, и платформа формирует ответ по bodyJsonTemplate. Если ошибок нет, пишем лог, и сообщение уходит в Platform.

Конструкторы ValidateParametersException

Исключение переопределяет части ответа, заданные в responseErrorSettings. Что не передали - берётся из настроек:

Конструктор

HTTP-код

notValidFields в ответе

(NotValidFields, string) - наш вариант

Из validationErrorCode

Список полей

(int, NotValidFields, string)

Из первого параметра

Список полей

(int, string)

Из первого параметра

Пусто

(string)

Из validationErrorCode

Пусто

Почему не обычный Exception? Мы проверили: неперехваченное исключение в коде обработчика возвращает клиенту HTTP 500 и текст исключения обычной строкой, без шаблона. Для клиента это выглядит как сбой сервера, а не как ошибка в данных.

Если же responseErrorSettings не настроен, ValidateParametersException всё равно переопределит HTTP‑код, но тело будет просто текстом сообщения, а список полей потеряется.

Проверяем результат

Отправляем запросы из Swagger внешней системы, через curl или Postman, например:

curl -X POST 'http://localhost:8888/habr/products' \
  -H 'Content-Type: application/json' \
  -d '{"meta":{"source":"warehouse","document_type":"product","created_at":"2026-10-21"},
       "items":[{"name":"Товар 1","code":"A-001","price":1500.00,"quantity":10}]}'

Ниже - ответы стенда на разные варианты данных. В каждом случае меняем одно-два поля корректного запроса выше.

#

Что передали

HTTP

notValidFields

Кто отклонил

1

Корректный запрос

200

-

-

2

created_at: "вчера"

400

meta.created_at

Платформа (тип)

3

created_at: "21.10.2026"

400

meta.created_at

Платформа (тип)

4

quantity: 1.5

400

items[0].quantity

Платформа (тип)

5

source, created_at, price, quantity = null

400

meta.source,meta.created_at,items[0].price,items[0].quantity

Платформа (тип)

6

Оборванный JSON {"meta": {

400

meta

Платформа (разбор)

7

В meta только source, в позиции только name

400

meta.document_type,meta.created_at,items[0].code,items[0].price,items[0].quantity

Код обработчика

8

items: []

400

items

Код обработчика

9

code: a-001, A-01, A-0001, AB-001, A001

400

items[0..4].code

Код обработчика

10

price: -5, quantity: 0

400

items[0].price,items[0].quantity

Код обработчика

11

created_at: "2030-01-01"

400

meta.created_at

Код обработчика

12

created_at: "2026-10-21T14:30:00"

200

-

-

  • Пример ответа платформы на случай 2 - errorMessage заполнила сама платформа, ни строчки нашего кода не выполнилось:

{
  "errorCode": 400,
  "errorMessage": "Could not convert string to DateTime: вчера. Path 'meta.created_at', line 1, position 76.",
  "notValidFields": "meta.created_at"
}
  • А это ответ на случай 7 - его сформировал наш ValidateParametersException:

{
  "errorCode": 400,
  "errorMessage": "данные не прошли проверку",
  "notValidFields": "meta.document_type,meta.created_at,items[0].code,items[0].price,items[0].quantity"
}

Несмотря на разные механизмы проверки, клиент получает ответ с одинаковой структурой. При этом текст ошибки зависит от того, кто обнаружил проблему: платформа возвращает технический текст на английском, а пользовательский обработчик - своё сообщение, например «данные не прошли проверку». Поэтому программно разбирать ответ стоит по errorCode и notValidFields, а errorMessage - показывать человеку или писать в лог.

Что ещё видно из таблицы:

  • Случай 7 - главный аргумент за проверки в коде. Отсутствующие поля платформа заполнила значениями по умолчанию ("", 1900-01-01, 0), и без наших проверок документ ушёл бы дальше с ценой 0.

  • Платформа собирает все ошибки типов сразу (случай 5), а не останавливается на первой.

  • Дата принимается в формате ISO 8601 (2026-10-21 или 2026-10-21T14:30:00). «Русский» формат 21.10.2026 отклоняется - это стоит зафиксировать в описании API для смежников.

  • Числа строкой тоже принимаются: "price": "1500.00" и "quantity": "10" проходят, если строка разбирается как число. Так что старые клиенты, которые слали числа в кавычках, переход на типизацию не сломает.

В Центре мониторинга по журналу внешней системы видны записи обработчика: Info (Зеленые метки) для принятых документов и Warning (Желтые метки) со списком полей для отклоненных.

Логи отображаемые в ЦМ в журнале REST адаптера

Логи отображаемые в ЦМ в журнале REST адаптера

Разбор одной «плохой» позиции

Пусть в items пришла такая позиция, где неверно почти всё:

{
  "name": "Товар 1",
  "code": "Ж-002",
  "price": "дорого",
  "quantity": "многа",
  "unknown_field": "abc"
}

Первый запрос.

Платформа не смогла превратить «дорого» в Decimal и «многа» в Int и ответила сама:

{
  "errorCode": 400,
  "errorMessage": "The supplied value is invalid for price.\nCould not convert string to integer: многа. Path 'items[0].quantity', line 1, position 160.",
  "notValidFields": "items[0].price,items[0].quantity"
}

Обратите внимание: items[0].code в списке нет. Запрос отклонён на уровне типов, и до нашей проверки формата кода дело не дошло. Это нормально, но клиента стоит предупредить: ошибки могут приходить в два захода.

Второй запрос.

Склад исправил цену и количество ("price": 1500, "quantity": 10), но код остался Ж-002. Теперь типы в порядке, запрос доходит до кода обработчика, и срабатывает регулярное выражение:

{
  "errorCode": 400,
  "errorMessage": "данные не прошли проверку",
  "notValidFields": "items[0].code"
}

Третий запрос с "code": "A-002" проходит с HTTP 200.

Итого по каждому полю:

Поле

Значение

Что происходит

name

«Товар 1»

Корректно

price

«дорого»

Отклоняет платформа: не число

quantity

«многа»

Отклоняет платформа: не целое число

code

«Ж-002»

Отклоняет код обработчика: кириллица не попадает в [A-Z] (только когда типы уже верны)

unknown_field

«abc»

Молча отбрасывается: в типе данных такого поля нет, ошибки не будет, дальше в Platform оно не попадёт

Про лишние поля стоит сказать отдельно:

Тип данных работает как фильтр: в процесс попадает только то, что в нём описано. Для интеграций это обычно плюс - смежная система может добавить новое поле, и интеграция не упадёт. Но из кода обработчика такие поля не увидеть: типизированная переменная их не содержит, а RunMessage.GetBodyAsString() пуст. Если по договорённости со смежником лишние поля должны быть ошибкой, это уже другая схема решения, и её стоит разбирать отдельно.

Итоги

Надёжная валидация в DATAREON Platform строится из трёх частей:

  1. Тип данных с корректной структурой. DateTime, Decimal и Int вместо String автоматически отклоняют «вчера», «дорого» и «многа» ещё до кода обработчика.

  2. Бизнес-правила в коде обработчика. Обязательность (с учётом значений по умолчанию), формат A-001, положительные цена и количество, дата не из будущего. Ошибки собираем в NotValidFields и выбрасываем ValidateParametersException.

  3. responseErrorSettings во внешней системе. Один шаблон bodyJsonTemplate для всех ошибок: типов, бизнес-правил, авторизации, ответов с ResponseCode 400–500.

Что в итоге получает клиент:

  • Корректный JSON - HTTP 200, сообщение уходит в дальнейшую обработку.

  • Некорректный JSON - HTTP 400 и предсказуемый ответ с перечнем полей с индексами элементов.

  • В журнале Центра мониторинга - запись с названием документа и статусом проверки.

Ещё одно преимущество подхода - валидация централизована на уровне интеграционной платформы и не дублируется в системах отправителя и получателя. Качество данных контролируется до их передачи в целевые системы, а правила проверки живут в едином интеграционном контуре: при изменении требований не нужно дорабатывать прикладные системы.

Главное, что стоит вынести: типизация и код не заменяют, а дополняют друг друга. Без типов придётся вручную разбирать каждую дату и число. Без кода документ без цены и даты уйдёт дальше с 0 и 1900-01-01.

Если у вас есть вопросы по статье или предложения для следующих кейсов - пишите в комментариях.

Источники

View the original on Хабр →

KioskNews shows a cleaned-up reading view extracted from the publisher’s page — the original always lives on their site, not ours.