JSON template engine that supports
template strings
withvariables
(configurablestring interpolation
),conditions
andloops
.
Шаблонизатор, позволяющий описывать JSON-ы с динамическим данными, подставляемыми в рантайме, путём описания специальных параметров.
yarn add priprava
template.json - Описание объекта // (раскрыть/закрыть)
{
"content": [
{
"component": "title",
"content": {
"$temp": true,
"template": "${ title }",
},
},
{
"$temp": true,
"if": "promo.has",
"template": {
"component": "promo",
"content": {
"$temp": true,
"template": "Осторожно, продакт-плейсмент: ${ promo.text }",
},
},
},
"В объектах ниже меняется только строка. Вывод, шаблонизируем его.",
{
"$temp": true,
"for": {
"item": "thisText",
"mode": "of",
"data": "text",
},
"template": {
"component": "text",
"content": {
"$temp": true,
"template": "${ thisText }",
},
},
},
"Сформировать массив - мало, хочется организовать условия на его содержимое!",
{
"component": "list",
"content": {
"$temp": true,
"for": {
"item": "thisItem",
"mode": "of",
"data": "list",
},
"template": {
"$temp": true,
"if": "thisItem.length <= 70",
"template": "${ thisItem }",
},
},
},
{
"component": "input",
"content": {
"title": "Заголовок поля",
"text": {
"$temp": true,
"template": "\"${ input }\" - интерполяция в строках в деле!",
},
"description": "Дополнительное описание",
},
},
],
}
data.json - Динамические данные // (раскрыть/закрыть)
{
"title": "Пример объекта, который должен сформировать класс.",
"promo": {
"has": true,
"text": "Сомневаетесь? Даже повара согласны, что priprava - это клёво!",
},
"text": [
"Это описание компоненты текста.",
"Полагаю, таких параграфов может быть несколько, поэтому необходимо это всё описывать циклом.",
"А в объекте ниже - списке, необходимо массив формировать прям во внутрь описания."
],
"list": [
"Элемент списка 1",
"Элемент списка 2",
"...",
"Тут ещё может быть куча элементов",
"А эта строка не будет выведена, так как длина текста превышает описанные в шаблоне условия."
],
"input": "Текст поля"
}
settings.ts - Параметры парсера // (раскрыть/закрыть)
export const settings: PripravaInputSettingsInterface = {
mode: {
default: PripravaModesEnum.ignore,
if: PripravaModesEnum.inherit,
for: PripravaModesEnum.break,
template: PripravaModesEnum.inherit,
},
interpolation: /\$\{([^\\}]*(?:\\.[^\\}]*)*)\}/g,
showStatus: false,
};
import Priprava from 'priprava';
import template from './template.json';
import data from './data.json';
import { settings } from './settings';
const priprava = new Priprava({
data,
settings,
});
const parsed = priprava.parse({
data: template,
});
if (!parsed) { // undefined
console.log('Результат парсинга - ничего');
}
console.log(parsed.data); // см. в пример результата
parsed - Результат парсинга // (раскрыть/закрыть)
{
content: [
{
component: 'title',
content: 'Пример объекта, который должен сформировать класс.',
},
{
component: 'promo',
content: "Осторожно, продакт-плейсмент: Сомневаетесь? Даже повара согласны, что priprava - это клёво!",
},
'В объектах ниже меняется только строка. Вывод, шаблонизируем его.',
{
component: 'text',
content: 'Это описание компоненты текста.',
},
{
component: 'text',
content: 'Полагаю, таких параграфов может быть несколько, поэтому необходимо это всё описывать циклом.',
},
{
component: 'text',
content: 'А в объекте ниже - списке, необходимо массив формировать прям во внутрь описания.',
},
{
component: 'list',
content: [
'Элемент списка 1',
'Элемент списка 2',
'...',
'Тут ещё может быть куча элементов',
],
},
{
component: 'input',
content: {
title: 'Заголовок поля',
text: '"Текст поля" - интерполяция в строках в деле!',
description: 'Дополнительное описание',
},
},
],
}
// ... - объявление всех данные.
const priprava = new Priprava({
data,
settings,
});
// ...
data
- стор (хранилище),object
со всеми динамическими данными;settings
- настройки парсера.
У Priprava
имеется публичный метод parse
, которому нужно передать шаблон, требующий обработки:
// продолжение листинга выше:
const parsed = priprava.parse({
data: template
});
// ... - дальнейшие действия с результатом.
Шаблонами могут являться данные любого типа. Либо парсер сможет найти в них шаблонизацию, либо не сможет. Конечно же, логичнее всего отправлять туда что-то интерфейса [key: string]: any
, чтобы в парсинге был смысл.
В случае безрезультатной обработки - вернётся undefined
. Если не получится найти шаблонизацию в данных, то они будут возращены в том виде, в котором были отосланы, как успешная обработка.
Про интерфейс данных, с которым работают все методы Priprava.
Распарсенный шаблон следует искать в data
результата. Ссылочка выше - не просто так.
$temp
=boolean
Флаг (true
, false
- не важно), указывающий парсеру на то, что данный объект является объектом шаблонизации.
Интерфейс: PripravaDescriptionInterface
Шаблонизация без шаблона - глупость, поэтому для объявления обязателен оператор template
.
Пример
{
"$temp": true,
"template": "# ${ title }"
}
if
=string
|boolean
Интерфейс: PripravaIfOperatorInterface
Строка интерполируется по умолчанию, т.е. исполняется целиком.
Пример
{
"$temp": true,
"if": "isPromo",
"template": "Реклама: ${ ad }"
}
for
= {item
,mode
,data
}
Интерфейс: PripravaForOperatorInterface
item
=string
;mode
="in"
|"of"
;data
=string
;
item
- название переменной итерируемого значения,
mode
- режим итерации, типаfor...in
илиfor...of
,
data
- ключ итерируемого объекта в сторе.
Пример
{
"$temp": true,
"for": {
"item": "thisArticle",
"mode": "of",
"data": "articles"
},
"template": "## ${ thisArticle.title }",
}
template
=string
|object
Интерфейс: PripravaTemplateInterface
По умолчанию, интерполяция строки осуществляется по формату шаблонных строк ES6 - ${...}
. Однако, если вам хочется что-то другое, например Angular-овская интерполяция - {{...}}
, то в настройках парсера можно задать кастомное регулярное выражение, пример: /{{([\s\S]+?)}}/g
.
Пример
{
"$temp": true,
"template": "ES${ 3 + 3 } template string"
}
mode
- Режимы работы парсера*default
- общий режим
по умолчанию:ignore
if
- для оператора условий
по умолчанию:inherit
for
- для оператора цикла
по умолчанию:break
template
- для оператора шаблона
по умолчанию:inherit
interpolation
- RegExp интерполяции в строке шаблона
по умолчанию:/\$\{([^\\}]*(?:\\.[^\\}]*)*)\}/g
- эт тип так будет:'ES${ 3 + 3 } template strings'
->'ES6 template strings'
showStatus
- Вывод логов (их почти нет, а те что есть - бесполезные)
по умолчанию:false
* значения режимов задаются enum-ом
PripravaModesEnum
:
ignore
- игнорирование исключительных ситуаций, "делаем, как можем";break
- переход к следующему объекту, "кладём на текущий";inherit
- наследование режима отmode.default
.
Решил замутить свой паттерн, на основе RORO. Суть в том, что методы принимают и возвращают объект следующего вида:
type InputInterface<
Data = unknown,
Settings = undefined
> = {
/** Входные данные */
data: Data;
/** Опции */
settings?: Settings;
};
Обязательность settings
определяется передачей дженерику интерфейса Settings
(2-ой).
- Правило не распространяется на статичные методы;
- Методы могут возвращать
undefined
, вместо описанного объекта.
Имеем 2 сервиса. Один из них получает данные от второго, маппит их определённым образом, а затем над результатом какие-то дополнительные операции. А теперь представим, что второй сервис чутка меняет структуру данных. Если маппинг реализован на современного ES или TS, то для правильной работы первого сервиса потребуется сбилдить новую версию с обновлённым маппигом. Ибо в проде спецификация ES не последних лет, компиляция и всё такое. Если же юзать JSON-ы для того, чтобы маппить данные, то достаточно будет в него внести соответствующие правки, после чего замаунтить в контейнер первого сервиса.
А может случиться так, что на каком-то серваке нужен первый сервис старой версии, тогда при маппинге в .js/.ts потребуется внесение обновлений в старую версию, так и в свежую.
Соблюдение SemVer не гарантируется, однако постараюсь. Но лучше фиксируйте версию.
Сорс, так-то, открытый, а вот полную гит-историю никто не обещал. Сорри. На GitHub-е с версии 0.4.1. История до того - в GitLab-е.
Если у кого-то возникнет желание поконтрибьютить немного - welcome.
Данный проект распространяется по MIT License - текст лицензии можно найти в файле LICENSE.
Template engine for describing JSON with dynamic data.
yarn add priprava
My own pattern is used - IO-DS, based on RORO. Methods receives and returns object.
The rule does not apply to:
static
methods;undefined
can be returned.
- Said Magomedov - Said-M
This project is licensed under the MIT License.