LINUX.ORG.RU

про парсеры json

 , ,


0

2

А есть ли такие чтобы при заданной схеме,лимитах и заранее выданных доп.инструкциях - или сразу делали бинарный блоб (структуру-структур-массивов C) или валились с ошибкой. Причём быстро ;-)

??

пока всё что находится в обозримом пространстве - они или делают свои динамические структуры, которые потом дополнительно надо мучительно обходить (с фичами lazy-parsing и прочее) или работают как sax-парсеры.

проверка схемы как-то сбоку прикручивается, хотя казалось-бы «зная схему можно резко ускорять парсинг».

PS/ как-то получилось что потоки json начали измеряться в Mbps и парсинг становится больным местом.

★★★★★
Ответ на: комментарий от hobbit

«компакт-диск CD-ROM»

да хоть и компакт диск объёмом если допустимо схемой. 660 Мб - это минута-пять с одной стороны и с другой на типичном хосте память от 32 Gb.

несмотря на вопли про «память дорожает», на самом деле пох. Тут действительно - если принимаете такие потоки, памяти должно быть много и это ваша проблема. Наше дело дело донести всё и быстро.

MKuznetsov ★★★★★
() автор топика

Схема полностью фиксированная вполть до порядка перечисления полей и размеров всех массивов? Или порядок может меняться, размеры меняться, какие-то поля отсутствовать или иметь меняющиеся типы?

firkax ★★★★★
()

Если в схеме будут всякие oneOf типа

{
          "title": "Typed object",
          "type": "object",
          "properties": {
            "type": {
              "type": "string",
              "enum": ["list", "number", "null"]
            },
            "value": {
              "oneOf": [
                {
                  "title": "value is array (when type is list)",
                  "type": "array",
                  "items": {}
                },
                {
                  "title": "value is number (when type is number)",
                  "type": "number"
                },
                {
                  "title": "value is null (when type is null)",
                  "type": "null"
                }
              ]
            }
          },
          "required": ["type", "value"],
          "additionalProperties": false
        }

то без динамических структур вряд ли можно распарсить

FishHook
()
Последнее исправление: FishHook (всего исправлений: 1)
Ответ на: комментарий от FishHook

будут всякие oneOf типа

в потоковых данных такого нет (или по крайней мере не встречал в конкретной области)..отправитель гарантирует довольно статическую схему.

там типа такого (слова придуманы, смысл оставлен)

{ "timestamp":100500,
  "seqId":35,
  "channel":"channel1",
  "updateOrSnapshot":"snapshot",
  "data":[
      ...data derived by channel and u/s follows..
   ]
}

уровней вложенности {} [] в высоком прыжке до 5-ти, в потоках больших вложенностей нет.

сами по себе схемы не меняются годами, никто не ломает совместимость просто так. С современной точки зрения конечно бывают и уродцы ;-) но они со старых годов с неизменными старыми схемами. Тщательно блюдут

MKuznetsov ★★★★★
() автор топика
Ответ на: комментарий от MKuznetsov

отправитель гарантирует довольно статическую схему

А авторы критикуемой библиотеки об этом знают? Если формат json позволяет закодировать вариантные данные, то парсер обязан их поддерживать. Как-то глупо даже объяснять столь очевидные истины. Парсеры пишутся для широкой аудитории, а не для вашего конкретного клиента который мамой клянется что не навертит кренделей в своих схемах. Если у вас в системе более строгий протокол, зачем вы используете json? Кодируйте в протобаф.

FishHook
()
Ответ на: комментарий от FishHook

А авторы критикуемой библиотеки об этом знают?

а тут нет критикуемых библиотек :-)

тут ищется нечто которое по с заданной и весьма ограниченной схемой json умеет парсить (конвертить) текст в бинарь. Любое «нешмогла» в процессе это ошибка.

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

На входе json - на выходе или заданный «бинарный блоб» (раз вам так слово понравилось), или вообще нихрена ничего.

первый отзыв похож на подобное, надо будет вчитаться+разобраться. На там rs, его и в проект не затащить (хотя и личный) и не факт что внутри именно так работает.

MKuznetsov ★★★★★
() автор топика
Ответ на: комментарий от MKuznetsov

его автомат разбора может быть построен даже на этапе компиляции

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

Если в Си-шную структуру десериализовывать, то я бы попробовал кодогенерировать надстройку над sax-интерфейсом rapidjson.

Проверки json-схемы как таковой при этом не будет

Manhunt ★★★★★
()
Последнее исправление: Manhunt (всего исправлений: 1)

Теоретически, можно придумать микрооптимизации, основанные на знании структуры (схемы) и правил форматирования (сортировка ключей, пробельные символы и т.д.), чтобы парсеру не пришлось строить дерево, а он в один проход вычитал интересующие ключи, но a) валидация JSON станет невозможна, а потому это можно использовать только во внутренней коммуникации b) однажды сервис, шлющий данные изменится, например разработчик решит вставить новое поле, и все эти оптимизации развалятся.

Хочешь скорости, используй бинарный протокол.

anonymous
()
Ответ на: комментарий от Manhunt

не то чтобы проверка схемы, а просто любая ошибка разбора «сразу-нах» :-)

прилетает, типично (да это крипта) :

{
  "arg": {
    "channel": "books",
    "instId": "BTC-USDT"
  },
  "action": "snapshot",
  "data": [
    {
      "asks": [
        ["8476.98", "415", "0", "13"],
        ["8477", "7", "0", "2"],
        ["8477.34", "85", "0", "1"],
        ["8477.56", "1", "0", "1"],
        ["8505.84", "8", "0", "1"],
        ["8506.37", "85", "0", "1"],
        ["8506.49", "2", "0", "1"],
        ["8506.96", "100", "0", "2"]
      ],
      "bids": [
        ["8476.97", "256", "0", "12"],
        ["8475.55", "101", "0", "1"],
        ["8475.54", "100", "0", "1"],
        ["8475.3", "1", "0", "1"],
        ["8447.32", "6", "0", "1"],
        ["8447.02", "246", "0", "1"],
        ["8446.83", "24", "0", "1"],
        ["8446", "95", "0", "3"]
      ],
      "ts": "1597026383085",
      "checksum": -855196043,
      "prevSeqId": -1,
      "seqId": 123456
    }
  ]
}

но это сокращённо, внутри массивы - до 500 записей (у некоторых даже больше). 100 раз в сек (10 msc/frame), и совсем не один канал. Парсинг становится на диво узким местом.

При этом схема не может быть изменена просто так, в области так не принято. Как был массив внутри, он так и останется, отдельных элементов в наборах меньше не будет, прежние поля не переименуются и не исчезнут. Даже в приведённом примере - нули «0» транслируются _исторически_так_сложилось_.

должна-же быть библиотека или тулза - ей даёшь схему json, она генерит частный парсер/конвертер. Со всякими AVX/SIMD оптимизациями - весь текст в памяти, схема известна.

MKuznetsov ★★★★★
() автор топика
Ответ на: комментарий от MKuznetsov

Для таких штук парсер на Си пишется за короткое время и занимающий мало строк. Желание писать не сразу прогу, а некую «схему» понятно, идея эта соблазнительная, но в реальности она скорее всего только увеличит затраченное на создание парсера время. Если, конечно, схема действительно меняется очень редко. Вместо написания парсера схемы удобнее будет сделать микробиблиотеку вспомогательных функций парсера json-а (в первую очередь парсер токенов, их тут немного видов).

firkax ★★★★★
()
Ответ на: комментарий от firkax

Для таких штук парсер на Си пишется за короткое время и занимающий мало строк

разных источников побольше 10-ти ;-( не писать-же на каждый свой парсер. Хотя данные отличаются мелкими нюансами и так-же годами стабильны.

навскидку - вот ещё один источник и об том-же самом :

# <-- Stream (snapshot)
{
  "channel": "book_lv2",
  "action": "snapshot",
  "data": [{
    "symbol": "BTC_USDT",
    "asks": [
      ["6.16", "0.6"],
      ["6.17", "1"],
      ["6.18", "1"],
      ...
    ],
    "bids": [
      ["5.65", "0.02"],
      ["5.61", "1.68"],
      ["5.6", "25.38"],
      ...
    ],
    "createTime": 1653029116343,
    "lastId": 10409,
    "id": 10410,
    "ts": 1652774727337
  }]
}

они описывают одну и ту-же сущность (в данном случае биржевой стакан, который везде одинаково устроен) просто от личных тараканов и местных обычаев отдают по разному.

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

MKuznetsov ★★★★★
() автор топика
Ответ на: комментарий от MKuznetsov

разных источников побольше 10-ти ;-( не писать-же на каждый свой парсер. Хотя данные отличаются мелкими нюансами и так-же годами стабильны.

Писать 10 парсеров конечно не нужно. Напишешь один, потом 10 раз мелкие правки в него внесёшь. Всё равно для разных данных разные же структуры нужны (или нет?).

А парсинг схем это либо тот же компилятор будет, только не из языка программирования а из языка описания схем (ну вообще препроцессор описателя схемы в си-код тоже можно, но по-моему тоже лишнее), либо дополнительные затраты времени на их обработку во время работы, при том что затея ради их снижения.

firkax ★★★★★
()
Последнее исправление: firkax (всего исправлений: 1)

Надо просто парсить используемую схему JSON на этапе компиляции, да и всё. Достаточно одного файлика с JSON где встречаются все возможные нужные варианты содержимого в виде исходника. Генерировать сишный сырец с декларацией структур и парсингом элементов JSON в соответствующие поля оных с игнорированием полей не присутсвовавших при компиляции. Разный тип данных в одном и том же поле JSON решается при помощи C union.

Когда-то даже попадался скриптик на перле где-то на github в сырцах прошивки для чего-то типа ESP8266, наверно можно попробовать найти по запросу что-то типа «C compile time JSON parser», хотя там делов-то на пару часов с нуля.

ЗЫ: Вот, сходу на пистоне что-то нашлось - https://github.com/badicsalex/json_schema_to_c , правда нужна JSON schema. Наверно несложно допилить чтобы жрало просто пример на JSON вместо схемы, если не противно в пистоне ковыряться.

Stanson ★★★★★
()
Ответ на: комментарий от Stanson

https://github.com/badicsalex/json_schema_to_c , правда нужна JSON schema. Наверно несложно допилить чтобы жрало просто пример на JSON вместо схемы, если не противно в пистоне ковыряться.

Оно построено на базе jsmn, а судя по бенчмаркам, скоростью jsmn не блещет: https://github.com/miloyip/nativejson-benchmark?tab=readme-ov-file#parsing-time

Manhunt ★★★★★
()
Ответ на: комментарий от Stanson

ЗЫ: Вот, сходу на пистоне что-то нашлось - https://github.com/badicsalex/json_schema_to_c , правда нужна JSON schema. Наверно несложно допилить чтобы жрало просто пример на JSON вместо схемы, если не противно в пистоне ковыряться.

идея близкая (скормили схему, получили что-то). Подозрительно что требуется jsmn. Из области догадок - фаза парсинга видимо отдельная внешняя, а получаемый «парсер» декорация над ним..

только вот разрешить загадки и оценить невозможно - json_schema_to_c не работает даже с собственным example :-)

Возможно за 5-6 лет с последнего коммита, питон уехал вперёд..штабильность и шовместимость.

MKuznetsov ★★★★★
() автор топика

Спросил искусственного идиота:

Существует ли C библиотека парсинга JSON, не 100% правильная и безопасная, но работающая по принципам scanf, и такая же быстрая?

Да — есть библиотека, почти буквально с таким API. Самый близкий вариант к тому, что ты описываешь:

1. frozen — JSON с scanf-подобным API

Самое точное совпадение с твоим описанием — библиотека Frozen JSON library.

Она специально сделана как scanf для JSON.

#include "frozen.h"

const char *json = "{ \"a\": 123, \"b\": \"hello\" }";

int a;
char b[32];

json_scanf(json, strlen(json),
           "{a:%d, b:%Q}", &a, b);

printf("a=%d b=%s\n", a, b);

Особенности

json_scanf() и json_vscanf() — формат как у scanf можно читать только нужные поля порядок ключей не важен работает без построения полного DOM маленькая библиотека, ориентирована на embedded

2. Сверхбыстрые C-библиотеки (но не scanf-style)

Если цель — максимальная скорость, но API другой:

yyjson

одна из самых быстрых C-библиотек

~1.7 GB/s парсинг на бенчмарках

Но API DOM/iterator, не scanf.

Shadow ★★★★★
()

например

struct Person {
  std::string first_name;
  std::string last_name;
  int age;
};

const auto homer =
    Person{.first_name = "Homer",
           .last_name = "Simpson",
           .age = 45};

// We can now write into and read from a JSON string.
const std::string json_string = rfl::json::write(homer);
auto homer2 = rfl::json::read<Person>(json_string).value();
Lrrr ★★★★★
()

не то чтобы решено, но подводя некоторый итог:

DOM - зло.

Все «парсеры json» просаживаются на формировании или обращениях к дереву dom, кто как. Невзирая на фичи lazy-parsing и мега-оптимизации.

Частные схемы (для ускорения разбора) не учитывают в парсинге никто, максимум ругаются при нарушении схем. В этом плане фокус не удался.

самое оптимальное по скорости и памяти - libjson-sax (вообще sax-парсеры). Но многовато кода, на каждый источник писать нудные однотипные event_handler и сразу формировать свои структуры. Зато с памятью шикардос:

each new nesting increases the parser memory use by 1 byte (for 4096 nested structures, you need 4K of memory).

где-то между ними frozen, писать мало+просто, lazy-parsing ловко спрятан под капот, работает с не-полным dom.

---

Кто советовал protobuf, messagepack - передающая сторона не моя, что присылают то и приходится разбирать :-) А так, с бинарными данными там разброд и шатания. В редких случаях есть protobuf, в некоторых свой отдельный протокол, некоторые даже fix/fast стримят.

MKuznetsov ★★★★★
() автор топика