В CraftHub теперь можно писать формулы. Не «редактор с табличкой», а таблица, которая считает сама: пишешь в ячейку =, и всё, что дальше, — это уже формула. =@[price] * @[qty], =SUM([total]), =EDATE(@[orderedAt], 1). Меняешь исходные числа — результат пересчитывается сам.
Синтаксис я честно списал с Excel: незачем заставлять людей учить ещё один язык ради того, что все и так знают. А вот с типами так не вышло — про это будет отдельный разговор ниже, потому что именно там самое спорное и самое, на мой взгляд, правильное решение.
Как это выглядит
Ставишь в начале ячейки = — ячейка становится формулой. Enter — применить, Escape — передумать. После этого в углу появляется маленький маркер, а по клику формула снова открывается для правки: и прямо в ячейке, и в строке формул над таблицей.
Из операторов есть всё, что ожидаешь: + - * / ^, & чтобы склеить текст, = <> < > <= >= для сравнений, скобки для группировки. Ничего необычного.
Всё подсказывается по ходу. Написал = — выпадает список функций с сигнатурой и короткой строчкой о том, что она делает. Открыл [ — список колонок. Список функций, кстати, не захардкожен в интерфейсе, а собирается из того же реестра, который использует движок, — так что рассинхрона «в подсказке одно, работает другое» быть не может.

Как сослаться на другие данные
В обычной таблице ячейки безымянные, поэтому там всё через буквы и цифры. Здесь колонки — это named-поля со своими типами, так что и ссылаться удобнее по имени.
@[поле] — это значение поля в текущей строке. Самая ходовая штука: =@[price] * @[qty]. Куда бы формула потом ни попала — протянул вниз, применил на весь столбец — @[поле] всё равно означает «моя строка».
[поле] без собачки — это уже весь столбец целиком, вместе со строками, которые добавят потом. Нужно оно внутри функций, которые едят много значений: =SUM([total]), =AVERAGE([price]). Разница с @ небольшая на вид, но существенная: =ROUND(@[total] / SUM([total]) * 100, 1) — это доля конкретной строки в общей сумме.
Ещё можно по-старому, через A1. B2 — вторая колонка, вторая строка. D1:D10 — диапазон. Так формула из одной строки дотягивается до другой: посчитать нарастающий итог, взять ставку из первой строки. Одна тонкость: буквы A, B, C привязаны не к самим полям, а к их порядку на экране. Перетащил поле A в конец — оно стало последней буквой, а бывшее C уехало в начало и стало A. Формулы после такой перестановки продолжают указывать на те же данные, просто буквы в них другие.
И $. Голый номер строки — относительный: =D1 из второй строки читается как «строка надо мной», и если протянуть формулу в пятую строку, она возьмёт D4. Поставил $ — зафиксировал: $D$1 из любой строки смотрит ровно в первую. Это то, что нужно для какого-нибудь курса или итога, который лежит в одном месте. Буквы колонок фиксированы всегда — «протягивания вправо», как в экселе, тут нет, поля-то разные.

Формула сразу на весь столбец
Тут есть два похожих жеста, которые легко перепутать.
Первый — Ctrl+Enter в строке формул (или кнопкой рядом, или из правого клика). Это применяет формулу ко всему столбцу разом, включая будущие строки. Получается «вычисляемая колонка».
Второй — потянуть за квадратик в углу ячейки. Это просто копирование формулы в те строки, по которым протащил, и не более.
Если в ячейку вычисляемой колонки вбить обычное значение руками, она из этой колоночной формулы выпадает — ровно как в экселе — и следующий пересчёт её не затрёт.
![[result]` — это все значения колонки, включая будущие строки](https://habrastorage.org/r/w1560/getpro/habr//post_images/12c/ebf/757/12cebf757e179eb591733724fb9d778f.png)
Функций больше восьмидесяти
Разбиты на шесть групп:
Математика:
SUM,PRODUCT,ROUND/ROUNDUP/ROUNDDOWN,CEILING,FLOOR,INT,TRUNC,MOD,POWER,SQRT,ABS,SIGN.Статистика:
AVERAGE,MEDIAN,MIN/MAX,COUNT/COUNTA/COUNTBLANK,COUNTIF/SUMIF/AVERAGEIF,STDEV,LARGE/SMALL,RANK.Логика:
IF,IFS,SWITCH,AND/OR/NOT/XOR,IFERROR,ISBLANK,ISNULL,ISNUMBER,ISTEXT,ISERROR.Текст:
CONCAT/TEXTJOIN,LEFT/RIGHT/MID,LEN,TRIM,UPPER/LOWER,FIND/SEARCH,REPLACE/SUBSTITUTE,SPLIT,REPT,TEXT,VALUE.JSON:
TOJSON/PARSEJSON,JSONPATH,KEYS,HASKEY,ARRAYLEN,TYPEOF,FLATTEN.Даты:
TODAY/NOW,DATE/DATETIME,YEAR/MONTH/DAY,HOUR/MINUTE/SECOND,WEEKDAY,EDATE/EOMONTH,DAYS/DATEDIF,DATEADD,ISDATE.
А вот с типами я с Excel не согласился
Синтаксис у экселя хороший, а вот с тем, как он обращается с типами, я жить не захотел. Приведение типов в экселе придумывали под сетку, где в ячейке может лежать что угодно. А тут в документе уже есть нормальная система типов — сам JSON. И когда эти две логики расходятся, я выбираю JSON.
Ничего не превращается само по себе. Строка "123" — это не число 123. Хочешь число — пиши VALUE("123"). TRUE — это не 1, для перехода есть IF(x, 1, 0). Сложить текст с числом, или null с числом — это ошибка (#VALUE! или #TYPE!), а не молчаливая догадка о том, что ты имел в виду.
«Нет ключа», null и пустая строка — это три разные вещи, и движок их не смешивает:
Что это |
|
| В | |
|---|---|---|---|---|
Missing | ключа в объекте вообще нет |
|
| пропускается |
Null | ключ есть, значение — |
|
| пропускается |
| ключ есть, значение — пустая строка |
|
| пропускается |
Обрати внимание: SUM и компания null и Missing пропускают, а не считают за ноль. В экселе пустая ячейка — это и ноль в арифметике, и «пропустить» в сумме одновременно, что довольно шизофренично. В JSON null — это осмысленные данные, а не «тут пока пусто».
Числа считаются на decimal, а не на double, поэтому деньги не уплывают в двадцатом знаке. Целое так и запишется целым: 5, а не 5.0. И 19.90 сохранит свой хвостовой ноль, а не схлопнется в 19.9.
Даты — это просто ISO-строки вида 2024-03-15 или 2024-03-15T14:30:00, а не серийные числа. Что на входе за формат — то и на выходе: та же точность, тот же часовой сдвиг. Складывать даты через + и - нельзя, для этого есть DATEADD, EDATE, DAYS, DATEDIF. И никаких «перекруток»: DATE(2024, 13, 1) — это ошибка, а не тихо превратившийся январь 2025-го.
Если результат не лезет в тип колонки — скажем, получилась дробь, а колонка Int — это #TYPE!. Формула не станет ничего округлять за тебя и уж точно не поменяет тип колонки.

Пересчёт и ошибки
Формулы знают, кто от кого зависит, — внутри это граф. Поменял одну ячейку — движок пройдёт по графу и пересчитает только то, что реально задето, а не весь документ. Если формулы зациклились друг на друга — это #CYCLE!, а не подвисший интерфейс.
Ошибки называются понятно: #DIV/0! — деление на ноль, #VALUE! — кривой аргумент, #TYPE! — результат не влезает в колонку, #REF! — ссылка ведёт в никуда, #CYCLE! — цикл. Ячейка с ошибкой остаётся пустой, а уголок краснеет; наведёшь — увидишь, что случилось. Ожидаемые обломы можно ловить самому через IFERROR.
В сам JSON ошибка попадает как null. Не строкой "#DIV/0!" — иначе бы она поломала тип колонки. А код и текст ошибки лежат отдельно, поэтому при повторном открытии файла всё сразу видно, без прогона пересчёта.
Файл остаётся обычным JSON
Это было для меня принципиально. В самом документе лежат только посчитанные значения — обычные числа и строки. Открыл его без CraftHub — там нормальный JSON, никаких следов формул.
Формулы живут в отдельном файле рядом: имя.formulas.json. Пишется он в тот же момент, что и документ. Переносишь проект — переноси оба.
Отдать данные наружу можно тремя способами: просто экспорт JSON (одни значения), «отвязать формулы» (значения остаются, файл с формулами удаляется), или бандл .crhb — это когда документ и формулы едут одним файлом, чтобы отдать кому-то всё целиком.
Немного про кишки
Движок формул вынесен в отдельные сборки и про интерфейс ничего не знает. Внутри всё как в учебнике: лексер, парсер, вычислитель. Добавить функцию — это одна запись в реестре, парсер и вычислитель трогать не надо. Отдельно живёт граф зависимостей и слой адресации, который переводит @[поле] в JSON-путь и обратно. Считается всё на decimal, даты гоняются через один-единственный ISO-парсер, чтобы не расползались форматы.
Попробовать
CraftHub — бесплатный редактор JSON под Windows, Linux и macOS, плюс расширение для VS Code. Формулы уже в свежей сборке, ничего доустанавливать не надо.
Расширение для VS Code: https://marketplace.visualstudio.com/items?itemName=c3n9.crafthub
Исходники: https://github.com/c3n9/CraftHub
Если вычисляемый JSON — это то, чего вам не хватало, закиньте звезду на GitHub. Мне это прямо помогает понять, что в фичу стоит вкладываться дальше: в планах формулы внутри вложенных объектов и массивов, именованные диапазоны и ещё функции.