# meon-json

[**EN**](https://github.com/vgnapuga/meon/blob/main/meon-json/README.md) | RU

Быстрый плоский JSON-ридер, построенный на движке
[`meon`](https://github.com/vgnapuga/meon/blob/main/meon/README.md).

`meon-json` строит *таблицу интервалов*, а не дерево. Движок выдаёт по одному
плоскому `Vec` на каждый вид элементов (объекты, массивы, строки, члены);
структура документа восстанавливается через вложенность интервалов по
источнику. Типизация скаляров (`nums` / `trues` / `falses` / `nulls`) — это
opt-in пост-проход, а не часть горячего цикла: кто никогда не типизирует, не
платит ничего.

* **meon**
  * [***GitHub***](https://github.com/vgnapuga/meon/blob/main/meon/README.md)
  * [***crates.io***](https://crates.io/crates/meon)
* **meon-macros**
  * [***GitHub***](https://github.com/vgnapuga/meon/blob/main/meon-macros/README.md)
  * [***crates.io***](https://crates.io/crates/meon-macros)
* **meon-md**
  * [***GitHub***](https://github.com/vgnapuga/meon/blob/main/meon-md/README.md)
  * [***crates.io***](https://crates.io/crates/meon-md)
* **meon-json**    <--
  * [***GitHub***](https://github.com/vgnapuga/meon/blob/main/meon-json/README.md)
  * [***crates.io***](https://crates.io/crates/meon-json)

* [***CHANGELOG.md***](https://github.com/vgnapuga/meon/blob/main/CHANGELOG.md)
* [***ARCHITECTURE.md***](https://github.com/vgnapuga/meon/blob/main/ARCHITECTURE.md)
* [***BENCHMARKS.md***](https://github.com/vgnapuga/meon/blob/main/benches/README.md)
* * [***MD_COMPARE.md***](https://github.com/vgnapuga/meon/blob/main/benches/MD_COMPARE.md)
* * [***JSON_COMPARE.md***](https://github.com/vgnapuga/meon/blob/main/benches/JSON_COMPARE.md)
* [***FUZZING.md***](https://github.com/vgnapuga/meon/blob/main/fuzz/README.md)

---

## Быстрый старт

```toml
[dependencies]
meon-json = "0.1"
```

```rust
use meon_json::JsonParser;

let src = br#"{"name":"Alice","age":30,"tags":["a","b"]}"#;
let c = JsonParser::parse(src);

// Доступ по виду элементов — O(1) на вид
println!("объектов: {}", c.objects.len());   // 1
println!("массивов: {}", c.arrays.len());    // 1
println!("членов:   {}", c.members.len());   // 3

// Разрешаем спаны ключа/значения члена в срезы — без копирования
for m in &c.members {
    println!("{} = {}", c.str(m.key).unwrap(), c.str(m.value).unwrap());
}
// "name" = "Alice"
// "age"  = 30
// "tags" = ["a","b"]
```

JSON с отступами и переносами строк разбирается так же — инлайн-скан движка
идёт по одному накопленному многострочному прогону, а не построчно, поэтому
стек структуры переживает каждый внутренний `\n` в документе без пустых строк.

---

## Что это — и чем это не является

`meon-json` — это **структурный ридер**, а не валидирующий парсер.

- Он сообщает структуру, которую *увидел*, и никогда не возвращает ошибку.
  Некорректный ввод — несбалансированные скобки, мусор в хвосте, дубликаты
  ключей, незакрытые контейнеры — не вызывает паники; выдаётся разумный
  частичный результат (незакрытый контейнер просто отбрасывается, а его
  зафиксированные члены сохраняются; мусор в хвосте попадает в `scalars`).
- Он **не** проверяет грамматику JSON. Если нужно отвергать невалидные
  документы — валидируйте отдельно; этот крейт для быстрого структурного и
  текстового извлечения по входу, которому вы уже доверяете или который
  валидируете в другом месте.
- Типизация скаляров — это **классификация по первому байту**, а не проверка
  числа: `1abc` типизируется как число, потому что начинается с цифры (см.
  [Известные ограничения](#известные-ограничения)).

Это повторяет on-demand модель второй стадии `simd-json`: структура находится
один раз; значения материализуются по типу только по запросу.

---

## Выходные поля

Эти структурные поля движок выдаёт напрямую. Все спаны — `u32` байтовые
смещения в заимствованный источник.

| Элемент            | Синтаксис          | Поле      | Тип           | Примечание                                                            |
|--------------------|--------------------|-----------|---------------|-----------------------------------------------------------------------|
| Объект             | `{ ... }`          | `objects` | `Vec<Span>`   | Только содержимое (без фигурных скобок — `objects_raw()` со скобками) |
| Массив             | `[ ... ]`          | `arrays`  | `Vec<Span>`   | Только содержимое (без скобок — `arrays_raw()` со скобками)           |
| Строка             | `"..."`            | `strings` | `Vec<Span>`   | Только содержимое (без кавычек); пустая `""` спан **не** выдаёт       |
| Член               | `key : value`      | `members` | `Vec<Member>` | По одному на пару; `key` / `value` — сырые спаны (с разделителями)    |
| Значение верх. ур. | голые `42`, `true` | `scalars` | `Vec<Span>`   | Инлайн-fallback: голые значения вне контейнеров и «хвосты»            |
| Прогон документа   | весь ввод          | `loose`   | `Vec<Span>`   | Блочный fallback; один спан на весь документ                          |

Отдельные **элементы массива** движок спанами не отслеживает — только
собственный внешний спан массива. Поэлементный доступ — задача пост-прохода
типизации (он разбивает спан каждого массива по его верхнеуровневым запятым);
см. [Типизация скаляров](#типизация-скаляров).

---

## Выходной тип

```rust
// Span — полуоткрытый байтовый диапазон [start, end) в срез источника
pub struct Span { pub start: u32, pub end: u32 }

// Member — одна пара `key: value`. Оба поля — СЫРЫЕ спаны источника:
//   key   — с кавычками для строкового ключа
//   value — со скобками/кавычками, когда значение — контейнер или строка
pub struct Member {
    pub key:   Span,
    pub value: Span,
}
```

Извлекайте неэкранированное или типизированное содержимое члена через
**вложенность интервалов** относительно других полей — например, член, чьё
значение — массив, побайтово равен срезу `arrays_raw()` этого массива; голый
спан `arrays` (только содержимое) строго *внутри* него.

---

## Работа со спанами

Контент-структура заимствует оригинальный источник. Используйте встроенные
хелперы:

```rust
let src = br#"{"a":[1,2,3]}"#;
let c = JsonParser::parse(src);

// str() возвращает None при невалидном UTF-8 вместо паники
println!("{}", c.str(c.members[0].key).unwrap());   // → "a"  (с кавычками)

// bytes() для сырого байтового доступа, без проверки UTF-8
let raw: &[u8] = c.bytes(c.members[0].value);        // → b"[1,2,3]"

// _clean() / _raw() итераторы на структурных спан-полях:
//   _clean — только содержимое (конвенция голого поля)
//   _raw   — с разделителями
let inner = c.arrays_clean().next().unwrap();        // → b"1,2,3"
let whole = c.arrays_raw().next().unwrap();          // → b"[1,2,3]"
```

> **Сырые, необрезанные значения членов.** Значение члена ничто не обрезает.
> Его спан идёт от позиции сразу после `:` (пропуская не более одного
> следующего пробела) до терминатора, дословно — хвостовые пробелы, табы, `\r`
> и встроенные `\n` все входят в сырой спан. Если нужен чистый, обрезанный по
> пробелам токен — используйте пост-проход типизации ниже.

---

## Типизация скаляров

Типизация живёт полностью вне движка, как методы на `JsonContent`. Запрашивайте
её только когда нужно:

```rust
use meon_json::{JsonParser, ScalarKind};

let src = br#"{"a":1,"b":true,"c":[2,3],"d":null}"#;
let c = JsonParser::parse(src);

// Все четыре вида за один кэш-дружественный проход, владеемые векторы наружу
let typed = c.type_scalars();
// typed.nums   → ["1", "2", "3"]   (значение члена + оба элемента массива)
// typed.trues  → ["true"]
// typed.nulls  → ["null"]
// typed.falses → []

// Или один вид, без аллокации остальных трёх
let nums = c.type_field(ScalarKind::Num);
```

```rust
// Возвращается из type_scalars(); каждый Vec держит спаны в источник,
// побайтово равные значению/элементу, из которого типизирован (пробелы обрезаны).
pub struct TypedScalars {
    pub nums:   Vec<Span>,
    pub trues:  Vec<Span>,
    pub falses: Vec<Span>,
    pub nulls:  Vec<Span>,
}

pub enum ScalarKind { Num, True, False, Null }
```

Типизация классифицирует **три** источника по первому байту, обрезав
`sep` / `tab` / `\n` / `\r` с обоих концов:

1. значения членов (`members[i].value`),
2. элементы массивов (спан каждого `arrays`, разбитый по своим
   верхнеуровневым запятым),
3. голые значения верхнего уровня (`scalars`).

Первый байт `"`, `{`, `[` — или что угодно нераспознанное — классифицируется
в `None` и пропускается, поэтому строки и контейнеры никогда не типизируются
ошибочно. Ничего не записывается обратно в `JsonContent`; он остаётся
неизменной записью ровно того, что увидел движок.

---

## Standalone-итераторы

Наследуются от движка: каждое структурное правило генерирует метод `find_*`,
который сканирует источник без полного парса. Полезно, когда из большого
документа нужен только один вид — например, извлечь все строки:

```rust
use meon_json::JsonParser;

let src = document.as_bytes();
for span in JsonParser::find_strings(src) {
    println!("{}", std::str::from_utf8(&src[span.start as usize..span.end as usize]).unwrap());
}
```

Standalone-итераторы работают без контекста: они находят свой один вид
элементов без знания о вложенности и окружающей структуре. Для JSON это важнее,
чем для плоского формата — `find_objects` / `find_arrays` находят только точный
объявленный разделитель и **не** отслеживают вложенность так, как полный
`parse`. Для корректной вложенности предпочитайте полный парс; за `find_*`
тянитесь только ради одиночного, нечувствительного к вложенности прохода (как
`find_strings`). См.
[`ARCHITECTURE.md §12`](https://github.com/vgnapuga/meon/blob/main/ARCHITECTURE.md#12-standalone-iterators).

---

## Feature flags

Наследуются от `meon`:

| Feature  | Эффект                                                      |
|----------|-------------------------------------------------------------|
| `avx2`   | 32-байтовый SIMD-поиск (требует nightly + AVX2 процессор)   |
| `avx512` | 64-байтовый SIMD-поиск (включает `avx2`)                    |

```toml
[dependencies]
meon-json = { version = "0.1", features = ["avx2"] }
```

---

## Вложенность

Эта грамматика устанавливает `max_nest = 64`: объекты и массивы вкладываются до
64 уровней, а значения членов `key_value` переживают сколь угодно глубокую
вложенность контейнеров в пределах этого предела. Документ, вложенный глубже 64
уровней, разбирается без паники, но контейнеры сверх предела остаются
неотслеженными, а не представляются собственными спанами.

```rust
let pretty = b"{\n  \"a\": {\n    \"b\": { \"c\": 1 }\n  }\n}";
let c = JsonParser::parse(pretty);
assert_eq!(c.objects.len(), 3);
assert_eq!(c.members.len(), 3);
```

---

## Известные ограничения

Это структурный ридер; ряд поведений намеренно следует из этого.

- **Не валидатор.** Невалидный ввод никогда не выдаёт ошибку — он даёт
  частичный результат. Несбалансированные скобки, мусор в хвосте, дубликаты
  ключей и незакрытые контейнеры дают разумный, не паникующий результат
  (дубликат ключа фиксирует оба члена по порядку; незакрытый контейнер
  отбрасывается, а его зафиксированные члены сохраняются).
- **Типизация — классификация по первому байту, а не проверка числа.** `1abc`
  типизируется как `Num`; `True` (с заглавной) не совпадает ни с одним
  правилом и остаётся нетипизированным. Классификатор проверяет один байт, а
  не остаток прогона.
- **Элементы массива не отслеживаются движком.** Движок хранит только внешний
  спан каждого массива; поэлементные спаны существуют исключительно через
  пост-проход типизации.
- **Значения членов сырые и необрезанные.** Пропускается только один ведущий
  пробел после `:` (`allow_sep`); хвостовые пробелы, `\r` и встроенные `\n`
  остаются в сыром спане. Слой типизации обрезает; структурное поле — нет.
- **Пустая строка `""` спан `strings` не выдаёт.** Её содержимое всё ещё
  восстановимо из сырого значения члена.
- **Глубина вложенности ограничена `max_nest = 64`.** Более глубокая структура
  специально не отслеживается.

Подробнее об оставшихся компромиссах движка —
[`ARCHITECTURE.md §17`](https://github.com/vgnapuga/meon/blob/main/ARCHITECTURE.md#17-known-limitations-and-deliberate-trade-offs).

---

## Лицензия

`meon-json` доступен под лицензией
[***GNU Affero General Public License v3.0 (AGPL-3.0)***](https://github.com/vgnapuga/meon/blob/main/LICENSE).

Если условия AGPL-3.0 несовместимы с вашим сценарием использования, доступна коммерческая лицензия — см. [***COMMERCIAL.md***](https://github.com/vgnapuga/meon/blob/main/COMMERCIAL.md).

Внося вклад в проект, вы соглашаетесь с [***Соглашением о лицензировании контрибуций (CLA)***](https://github.com/vgnapuga/meon/blob/main/CLA.md).
