Имя: Пароль:
1C
1C 7.7
v7: Парсинг JSON с помошью SQLLite и значения булево
0 MWWRuza
 
10.02.24
01:35
Добрый день.
Это в продолжение моей темы: Можно ли получить инфу о контрагенте по его GUID в ЦРПТ - ?
но, не по тому вопросу, из-за которого та тема создана. Просто вылезло на том-же отчете.
Писал и отлаживал на не большой базе, все работало.
Поставил реальному клиенту - 1С стала падать на нем... Стал разбираться почему... Оказалость, парсер JSON, которым я давно и успешно пользовался, не переваривает большие файлы, а они там действительно большие...
Ладно, хороший повод воспользоваться SQLLite. Переделал отчет на ее использование, и все заработало, ничего не падает.
И все бы хорошо, но в старом парсере булево было представлено как "-1" - true, и "0" - false. SQLLite возвращает как "1" и "0"... Как отличить "1" что это "Да", от того, что "1" это число? Состав таблицы отчета не постоянный, показатели и их значения "плавающие", зависят от многих факторов, так, что "по смыслу" не понять что это - число или булево... Ну, с нулем то еще можно мириться, там особо нет значений нулевых, а вот единица встречается часто... И не понять, где "Да", а где число 1... Раньше было "-1", которое не всречается в этом отчете, выводил тупо "Да" на "-1", и как-бы устраивало.







Что-бы такого придумать, что-бы "обойти" эту ситуацию-?
Пока видится только вариант создания списка реквизитов, которые могут иметь значение только булево, и проверять их по этому списку при выводе отчета, типо если есть имя этого поля(Ключ) в этом списке, то интерпретировать 1/0 как Да/Нет, а если нет в списке, то выводить без преобразования, числами... Но, как-то это криво... Наши закоготворцы не дремлют, и их прогеры тоже стараются во всю, в любой момент могут добавить какие-то дополнительные показатели, которых нет в списке, и опять добавляй...
Может есть какой-то более правильный "костыль" - ?
Или может каким-то параметром запроса SQLLite можно отключить это преобразование, что-бы просто строками выводил "true/false" - ?
2 Волшебник
 
10.02.24
10:05
(0) Надо анализировать имя поля. Если там есть "Flag" или "is", то скорее всего это булево.
С помощью статистики и человека составить список таких реквизитов.
3 vbus
 
10.02.24
10:10
Пусть парсер пишет Истина или Ложь в поле TEXT, этого реквизита.
4 Волшебник
 
10.02.24
10:15
Ещё можно анализировать значение в других элементах. Если там встречается 2 или другие цифры, то это наверняка число, а если только 1 и 0, то есть повышенная вероятность булева типа.
5 MWWRuza
 
10.02.24
11:29
(2) С помощью статистики и человека составить список таких реквизитов.

Ну, тут есть вариант проще - в любой более-менее крупной конторе(да хотя-бы в той, на которой предыдущий парсер падал - там порядка ~2500 карточек), сохранить этот JSON ответа в файл, открыть его тем-же Нотепад++ с плагином, отформатировать, что-бы он разбился на кучу строк, а потом написать обработку, которая пробежит по строкам этого файла как по обычному тексту, без парсинга, и просто соберет все имена ревизитов, из строк, в которых есть  "true" или "false"... Ну, а потом, просто при выводе отчета анализировать есть текущий реквизит в этом списке или нет, и соответственно его обрабатывать.
Не вижу проблем, час работы... Но, это:





Я просто боюсь, что порачу я этот час, реализую задумку, а потом придет Djelf, "покрутит пальцем у виска", и скажет - "ребята, вы чего? Все это в топку.. Достаточно в SQLLite применить параметр <ХЗ>, и оно само будет булево передавать без перекодировки в число" :-)
6 Garykom
 
10.02.24
11:41
(0) микросервис на Go
7 Крэкпэк
 
10.02.24
12:31
(0) Разбейте строку


Процедура СкриптФормироватьСтроку(прСтрПарам, прПерем, scriptCtrl)
    Если (ПустоеЗначение(scriptCtrl) = 1) Тогда
        scriptCtrl =  СоздатьОбъект("MSScriptControl.ScriptControl");
        scriptCtrl.Language="javascript";
    КонецЕсли;
    scriptCtrl.Reset();
    scriptCtrl.Eval("var "+прПерем+"=''");
    
    лпСтрПарам = прСтрПарам;
    Пока (СтрДлина(лпСтрПарам)>0)  Цикл
        Если СтрДлина(лпСтрПарам) <= 200000 Тогда
            лпЛевСтрПарам = лпСтрПарам;
            лпСтрПарам = "";
        Иначе
            лпЛевСтрПарам = Лев(лпСтрПарам, 200000);
            лпСтрПарам = Сред(лпСтрПарам, 200001);
        КонецЕсли;
        
        лпЛевСтрПарам = СтрЗаменить(лпЛевСтрПарам,"\","\\");  //для JS
        
        scriptCtrl.Eval(прПерем+"="+прПерем+" + '"+лпЛевСтрПарам+"'");
    КонецЦикла;
КонецПроцедуры

Процедура СкриптКонтролИнит(scriptCtrl)
    код = "              
    |
    | function parseJSON_(strJSON) {
    |  var objJSON = eval('(function(){return ' + strJSON + ';})()');
    |  return(objJSON);
    | }
    |
    | function parseJSON() {
    |  var objJSON = eval('(' + strJSON + ')');
    |  return(objJSON);
    | }
    |
    | // Получить элемент массива
    | function aGet(Array, index) {
    |  return(Array[index]);
    | }
    |
    | // Получить ключ пары по индексу
    | function oKey(Obj, index) {
    |  var size = 0, key;
    |  for (key in Obj) {
    |   if (size == index) break;
    |   if (Obj.hasOwnProperty(key)) size++;
    |  }
    |  return(key);
    | }
    |
    | // Получить значение пары по ключу
    | function oValueByKey(Obj, key) {
    |  if (Obj[key] === true||Obj[key] === false)  return Obj[key].toString();
    |  return(Obj[key]);
    | }
    |
    | //Получить количество пар в объекте
    | Object.size = function(obj) {
    |  var size = 0, key;
    |  for (key in obj) {
    |   if (obj.hasOwnProperty(key)) size++;
    |  }
    |  return(size);
    | }
    |
    | //Получить размер объекта (количество пар в нём)
    | function oSize(Obj) {
    |  return(Object.size(Obj));
    | }
    |
    | // Получить тип объекта (number, string, object, array)
    | function eType(Element) {
    |  if (Element instanceof Array) {
    |    return(""array"");
    |  } else if (Object.prototype.toString.call(Element) === '[object Array]') {
    |    return(""array"");
    |  } else {
    |   return(typeof(Element));
    |  }
    | }
    |";
    scriptCtrl.AddCode(код);
КонецПроцедуры

и далее вызов

Стр=СтрЗаменить(стр,СимволТабуляции,"");
Стр=СтрЗаменить(Стр,Симв(10),"");
Стр=СтрЗаменить(Стр,Симв(13),"");
Стр=СтрЗаменить(Стр,"\r","");
Стр=СтрЗаменить(Стр,"\n","");
        
scriptCtrl="";
        СкриптФормироватьСтроку(Стр,"strJSON",scriptCtrl);
СкриптКонтролИнит(scriptCtrl);
        
obj = scriptCtrl.run("parseJSON");    

P.S. сори за неформатированный текст
8 MWWRuza
 
10.02.24
17:01
Все оказалось проще :-)

(5) Я просто боюсь, что потрачу я этот час, реализую задумку, а потом придет Djelf, "покрутит пальцем у виска", и скажет - "ребята, вы чего? Все это в топку..

Так и есть, в более свежих сборках SQLLite, в ТЗ ответа запроса, есть колонка "type":





А дальше "уже дело техники" - на основании этих двух колонок уже в 1С можно формировать Да/Нет или число :-)
Супер! Все просто и удобно.
Ровно шесть коротких строчек кода, и все идеально:


И даже, в отличии от предыдущего парсера, если в значениях встретится "0" или "-1", то все равно сформируется в печатной форме правильно, где надо число, а где надо - булево.
9 Волшебник
 
10.02.24
15:38
(8) 👍
11 Aleksey
 
10.02.24
16:24
(8) А где почитать про "Парсинг JSON с помошью SQLLite"
12 MWWRuza
 
10.02.24
16:58
(11) Там все очень просто - у Djelf в демке обработки по карлику было...
А вообще, вот у меня так сделано:

Функция РазборJSON_SQLLite(ТекстJSON)
    Попытка
        База = СоздатьОбъект("SQLiteBase");
    Исключение
        ЗагрузитьВнешнююКомпоненту("1sqlite.dll");
        База = СоздатьОбъект("SQLiteBase");
    КонецПопытки;
    База.Открыть(":memory:");
    Запрос = База.НовыйЗапрос();
    Запрос.Подготовить("
    |SELECT
    |    *
    |FROM json_tree(@ТекстJSON)
    |");
    Запрос.УстановитьПараметр("@ТекстJSON",ТекстJSON);
    тзДерево = Запрос.Выполнить();
    Возврат тзДерево;    
КонецФункции

Естественно, 1sqlite.dll должна быть достаточно свежая, старые этого не понимают, ошибку пишут.
13 Крэкпэк
 
10.02.24
17:18
(12) и как данный код справляется при передачи большой строки ?
14 MWWRuza
 
10.02.24
17:30
(13) Хм... Вопрос конечно интересный, пока тестировал строку, которая в файле при сохранениии весит 1 мегабайт... Если с большей не будет справляться, из-за ограничения длины строки,то буду думать как переделать на файл или поток.

Но, одно могу сказать - парсер отсюда: https://github.com/r72cccp/1C77_JSON_parser/
падает и на половине размера этих данных, из-за чего и стал переделывать на SQLLite...
15 Крэкпэк
 
10.02.24
17:41
(14) попробуй мой код (7) там как раз для длинных строк
16 MWWRuza
 
10.02.24
18:17
Вообще, конечно, косяк архитектуры API ЧЗ... Я запрашиваю остатки/движения, возвращаются движения по GTIN, без наименований... Ну, хорошо, по GTIN можно получить карточки товаров, и из них взять "человеко-читаемые" наименования товаров. Но, это огромный файл, так, как карточки товаров содержат кроме GTIN и наименования, еще кучу данных. Да, это упрощает расшифровку значений ячеек таблицы - не надо ничего запрашивать, данные и так уже получены.
Но, насколько легче все это было-бы, если бы ответ содержал не только GTIN, но и Наименования, а делать запрос конкретной карточки с полными данными по ее GTIN только при необходимости, при расшифровке(обработке ячейки таблицы)?
Да, я понимаю, когда мы смотрим фильмы по сети, трафик в сотни, а то и в тысячи раз больше... Но, все равно, зачем гонять по сети лишнюю, никому не нужную инфу?
17 MWWRuza
 
10.02.24
17:49
(15) Попробую. Потом отпишусь.
18 Djelf
 
10.02.24
20:44
(12) Разбор JSON довольно старая штука в sqlite, но пока не было ни моих потребностей, ни потребностей пользователей и я это не задействовал.
И json не входил с сборку (не помню когда я стал его по-умолчанию включать), из-за этого и была ошибка.
Сейчас это стало уже везде востребовано и всегда уже будет во всех сборках (ничтожный оверхед по объему).
Из интересного и свежего: в sqlite3 сейчас появился разбор в jsonb(ТекстJson) это блоб и может быть сохранен в БП sqlite3, размер меньше, скорость выборки в 10 раз (обещают) быстрее.
Будем посмотреть...

Волшебник, спасибо за возращение прав на редактирование! Я всякую ерунду писал, постараюсь больше такого не делать.
19 Aleksey
 
10.02.24
20:57
(15) Докладаю. У меня есть загрузка заказов с сайта. и когда большой заказ (примерно 150-200 строк и больше), то 1С ка падала на таких заказах (сайт ответ дает в JSONе).
С твоим кодом заказ на 257 позиций загрузился и не споткнулся. По крайне мере визуально по сумме заказ и количеству позиций сошлось.
20 MWWRuza
 
11.02.24
00:36
(19) (15) Докладаю.

Ну, тогда и я немного добавлю.
Протестировал сегодня у клиента с довольно солидным документооборотом.
Примерно 2400 карточек маркированных товаров ЧЗ отдает.
По любому, больше 1000 в запрос не засунуть - у них ограничение. Скриншот из PDF-ки описания API:





Поэтому, приходится делать несколько запросов, делить всю эту кучу на несколько по 1000 + остаток, через в цикле СчGTIN%1000 = 0.
Получается два запроса по 1000, и один 400 кодов.
Сохранил ради интереса строки JSON в файлы, и что получилось:





Один из "больших" файлов в Нотепад++ :





Почти три миллиона символов.
"Склеиваю" полученные таблицы в одну, я уже потом, после парсинга, перед выводом печатной формы отчета.
Глотает и не давится, если не считать те секунды, в течении которых сервер отрабатывает запросы, то парсинг с помощью SQLLite отрабатывает практически мгновенно :-)

Djelf, еще раз спасибо за очень полезную вещь :-)
21 MWWRuza
 
11.02.24
00:47
Да, кстати, по теме сабжа. Переделал по совету Djelf запрос так:

Функция РазборJSON_SQLLite(ТекстJSON)
    Попытка
        База = СоздатьОбъект("SQLiteBase");
    Исключение
        ЗагрузитьВнешнююКомпоненту("1sqlite.dll");
        База = СоздатьОбъект("SQLiteBase");
    КонецПопытки;
    База.Открыть(":memory:");
    Запрос= База.НовыйЗапрос();
    Запрос.Подготовить("
    |SELECT
    |    CASE
    |        WHEN tree.type == 'true' THEN 'Да'
    |        WHEN tree.type == 'false' THEN 'Нет'
    |        ELSE tree.value
    |    END AS Значение,
      |    tree.key AS Ключ,
    |    tree.path AS Путь,
    |    tree.type AS Тип
    |FROM json_tree(@ТекстJSON) AS tree
      |WHERE tree.key > ''
    |");
    Запрос.УстановитьПараметр("@ТекстJSON",ТекстJSON);  
    тзДерево = Запрос.Выполнить();
    Возврат тзДерево;    
КонецФункции

Теперь все булево/числа внутри запроса преобразуются, и прилетают в 1С в готовом виде - "Да" или "Нет" или число :-)
22 Крэкпэк
 
11.02.24
15:01
(21) ну если есть 1с++, база SQL и уровень совместимости БД MS SQL Server 2016 и выше то будет работать такое

    Запрос=СоздатьОбъект("ODBCRecordset");
    ТекстЗапроса="
    |SELECT *
    |FROM OPENJSON(:Стр, '$.""Содержимое""')
    |WITH
    |(
    |    [Идентификатор]        varchar(36)
    |, [НомерДокумента]        varchar(20)
    |)
    |";
    
    Запрос.УстановитьТекстовыйПараметр("Стр",СокрЛП(Стр));
Рез=Запрос.ВыполнитьИнструкцию(ТекстЗапроса);

где Стр = "
{
    "Содержимое": [
        {
            "Идентификатор": "8D943717-37C5-4D43-B5EF-341B3F065C0F",
            "НомерДокумента": "Ю000000611"
        }
    ]
}

и никаких дополнительных прокладок не нужно
23 Djelf
 
11.02.24
16:42
(22) А "БД MS SQL Server 2016 и выше это не прокладка?" Тоже прокладка, просто это другая прокладка.
Но так тоже можно, и еще двумя десятками различных вариантов прокладок...
24 MWWRuza
 
11.02.24
16:57
(22) база SQL и уровень совместимости БД MS SQL Server 2016 и выше

Там много чего можно... Но, в том-то и дело что магазинчики маленькие, и ни о каких "базах SQL" речи не идет. База обычная DBF.
25 Крэкпэк
 
11.02.24
17:14
(23) имелось ввиду что база уже работает под управлением SQL
а так конечно согласен по поводу кучи вариантов
(24) с DBF конечно не вариант

с другой стороны может кому то пригодится
26 Djelf
 
12.02.24
05:33
Так с чем сравнивать то? Меньше полутора метров всего занимает 1sqlite, без установки в системе и регистрации в реестре.
Ну давайте, поставьте MS SQL на тех же условиях, или еще что-то...
Конкурентов тут нет.
27 serpentt
 
12.02.24
11:45
Здравствуйте, какая сейчас актуальная версия 1sqlite и где ?
28 Djelf
 
12.02.24
11:57
(27) Последняя... Тут все: https://cloud.mail.ru/public/9znr/ZJ6ULE9aR