Кастомный тип свойства для JSON-данных с валидацией

24.11.2025

Проблема

При работе со сложными структурированными данными в инфоблоках часто приходится хранить JSON в обычных строковых свойствах. Это создает проблемы: нет валидации при сохранении, неудобный интерфейс редактирования и риск повреждения данных. Стандартные типы свойств Битрикс не предоставляют встроенных механизмов для работы с JSON-структурами.

Решение

Создание кастомного типа свойства позволяет реализовать специализированный интерфейс с валидацией и удобным редактором. Регистрируем новый тип свойства через событие.

Регистрация типа свойства:

<?php
// /local/php_interface/init.php
$eventManager = \Bitrix\Main\EventManager::getInstance();

$eventManager->addEventHandler(
    'iblock',
    'OnIBlockPropertyBuildList',
    ['CustomPropertyJson', 'GetUserTypeDescription']
);

class CustomPropertyJson
{
    public static function GetUserTypeDescription(): array
    {
        return [
            'PROPERTY_TYPE' => 'S',
            'USER_TYPE' => 'json_data',
            'DESCRIPTION' => 'JSON данные',
            'GetPropertyFieldHtml' => [__CLASS__, 'GetPropertyFieldHtml'],
            'ConvertToDB' => [__CLASS__, 'ConvertToDB'],
            'ConvertFromDB' => [__CLASS__, 'ConvertFromDB'],
        ];
    }

    // Валидация и сохранение
    public static function ConvertToDB($property, $value)
    {
        if (empty($value['VALUE'])) {
            return $value;
        }

        // Проверяем валидность JSON
        $decoded = json_decode($value['VALUE'], true);
        if (json_last_error() !== JSON_ERROR_NONE) {
            global $APPLICATION;
            $APPLICATION->ThrowException('Невалидный JSON: ' . json_last_error_msg());
            return false;
        }

        // Дополнительная валидация структуры
        if (isset($property['USER_TYPE_SETTINGS']['REQUIRED_FIELDS'])) {
            $required = explode(',', $property['USER_TYPE_SETTINGS']['REQUIRED_FIELDS']);
            foreach ($required as $field) {
                if (!isset($decoded[trim($field)])) {
                    global $APPLICATION;
                    $APPLICATION->ThrowException("Отсутствует обязательное поле: {$field}");
                    return false;
                }
            }
        }

        return $value;
    }

    // Форматирование при выводе
    public static function ConvertFromDB($property, $value)
    {
        $value['VALUE'] = htmlspecialcharsback($value['VALUE']);
        return $value;
    }

    // Интерфейс редактирования
    public static function GetPropertyFieldHtml($property, $value, $strHTMLControlName)
    {
        $currentValue = $value['VALUE'];

        return '<textarea 
    name="' . $strHTMLControlName['VALUE'] . '" 
    rows="10" 
    cols="50" 
    class="json-editor"
    placeholder=\'{"key": "value"}\'
    style="font-family: monospace; font-size: 14px; line-height: 1.4; padding: 8px; border: 1px solid #ccc; border-radius: 4px; width: 100%;">' .
            htmlspecialchars($currentValue) .
            '</textarea>
<script>
    BX.ready(function() {
        const textarea = document.querySelector(\'textarea[name="' . $strHTMLControlName['VALUE'] . '"]\');
        
        // Автоформатирование при потере фокуса
        textarea.addEventListener("blur", function() {
            try {
                if (this.value.trim()) {
                    const obj = JSON.parse(this.value);
                    this.value = JSON.stringify(obj, null, 2);
                    this.style.borderColor = "#28a745";
                }
            } catch(e) {
                this.style.borderColor = "#dc3545";
                console.error("JSON error:", e);
            }
        });

        // Проверка при вводе
        textarea.addEventListener("input", function() {
            try {
                if (this.value.trim()) {
                    JSON.parse(this.value);
                    this.style.borderColor = "#28a745";
                } else {
                    this.style.borderColor = "#ccc";
                }
            } catch(e) {
                this.style.borderColor = "#dc3545";
            }
        });

        // Инициализация проверки
        textarea.dispatchEvent(new Event("input"));
    });
</script>';
    }
}

Итог

Кастомный тип свойства обеспечивает типобезопасность, валидацию данных на уровне сохранения и улучшает UX редактирования. Подход масштабируется для любых специфических форматов данных.

Telegram Все советы
Опубликовано 23 часа назад