-
Новый интерфейс Ютуба
Пишут, что Ютуб готовит новый интерфейс плеера. Разумеется, он более “воздушный”: кнопки обтянуты капсулами, вокруг которых пустота. Новые кнопки отнимают еще больше места от видео.
Наверное, в десятый раз повторю тезис: современных фронтендеров (ну или веб-дизайнеров) нужно не учить, а лечить. Когда дизайнер отхватывает десятую часть экрана под кнопки и не видит в этом проблемы, ему нужна помощь специалистов. Что он делает в профессии, я не знаю.
Разумеется, кнопки не должны наползать на видео, их место в отдельной панели снизу. Сюда же относится пауза: когда я нажал ее, не должно появляться затемнений, выпадашек с рекомендациями, блямбы с двумя палками и так далее.
Все это бесконечно просто для понимания и бесконечно сложно для дизайнеров.
-
Mastercard нанимает
Я подписан на кложурные вакансии в Linked In. Не планирую менять работу, просто чтобы иметь представление, кто нанимает. В числе прочих увидел Mastercard (SDET Backend API’s):
We are the Platform & API Team which is responsible for the customer facing APIs to our products and are looking for a Sr Software Engineer for our Seattle or Vancouver Canada offices. As a Sr Software Engineer, we are looking for someone who thrives on designing, coding and maintaining high performance data processing applications in functional programming on the JVM platform (which is primarily in Clоjure), running on AWS. Our ideal candidate would have deep experience building internal and external latency sensitive APIs with functional programming using the most fitting tools and having the passion to champion new and exciting technologies to solve our unique and challenging problems.
Так что вот! Источник: https://www.linkedin.com/jobs/view/4174329635/
-
Кэширование
Небольшая заметка о кэшировании. Для начала тезис:
Кэш — это лекарство, которое подчас опасней болезни. Пользоваться им нужно аккуратно, а еще лучше — не попадать в ситуацию, когда он нужен.
Добавление кэша в проект несет много проблем. Например, на сколько его устанавливать? Если данные обновились, как его сбросить? Кажется, что ответить просто: давай на час, плюс добавим хук в ORM, чтобы после сохранения модели сбрасывался такой-то ключ.
На практике все сложнее. Данные могут поступать не только через ORM, но и пакетным импортом или вставкой в таблицу из скрипта. Легко прозевать сброс ключа, и клиент увидит старые данные. Можно оказаться в ситуации, когда кэши будут зависимы: один строится на другом, и какой сбрасывать — не ясно.
Помню, когда программировал на Python и Django, считалось нормой обмазываться кэшами на каждый чих. Фреймворк всячески поощрял это: были различные бэкенды кэша, декораторы, которые навесил на функцию — и готово. У кэшей даже были версии, представляете? С ними отстрелить ногу было еще проще.
По наивности разработчики думают, что если запрос к базе медленный, то его нужно кэшировать. Это крайне наивно. База данных для того и создана, чтобы выполнять запросы. Современные базы могут выполнять одновременно много, очень много запросов. Например, в текущем проекте база, к которой я изрядно приложил руку, держит 300 транзакций в секунду — а внутри огромные JSON-документы.
Вот что можно предположить, если база отвечает медленно:
- не используется пул соединений
- на каждый запрос открывается новое соединение
- запрос делает полное сканирование таблицы (нет индекса)
- индекс есть, но из-за кривого where он не работает
- разработчик делает 100500 get-by-id в цикле
- в запрос передают 65К параметров вместо массива или json
- неотимальная вставка, например insert в цикле вместо copy from
Ни один из этих пунктов не рассосется, если добавить кэш. Поэтому сначала нужно разобраться, что идет не так. А когда разобрались, проще исправить на месте, чем добавлять кэш.
Разумеется, бывают очень сложные запросы, которые нельзя выполнить быстро в силу их природы. Но погодите со своим кэшем: такой запрос легко материализовать в таблицу и время от времени обновлять. На таблицу можно повесить индекс и выбирать из нее. Выходит, кэш легко организовать в базе.
В широком смысле сетевой кэш — это вторая база данных. Если вы не справились с одной базой, с чего вы решили, что с двумя будет проще?
Какие еще проблемы кэширования можно назвать? Да хотя бы следующие.
Сериализация. Когда вы пишете данные в Memcache, как они сериализируется? Если это json, вы теряете даты и типы коллекций, например, множество становится списком. Если у вас словарь с целыми числами в ключах, это тоже станет проблемой: обратно вы получите строки. Хорошо, когда есть библиотека для бинарной сериализации. Но если один и тот же кэш читают в разных языках, это станет проблемой: вряд ли такая библиотека есть под все языки (кроме protobuf).
Прогрев. Пока кэш актуален, все хорошо. Но когда тысячи клиентов начнут обновлять его, кэш-серверу станет плохо. Нужен фоновый процесс, который обновит кэш в фоне за несколько минут до того, как он протух. Кто будет следить за этим процессом?
Транзакционность. В одном проекте мы так увлеклись кэшами, что словили глюк, когда несколько клиентов обновляли один и тот же ключ. Чтобы это разрулить, ведущий разработчик написал джанговский Cache с подобием транзакции. Когда кто-то хотел прочитать ключ
foo_bar
, код проверял, есть ли ключfoo_bar__lock
. Если да, это значило, что в этот момент кто-то обновляет ключfoo_bar
, и в доступе отказывали. Это был костыль поверх костыля, но мы очень гордились.Полнота. Другой пример, о котором писал в комментариях. Когда-то в Wargaming мы выкатили новый проект. Главная страница была тяжелой, потому что собирала данные из десяти сервисов, плюс было несколько языков. Всю страницу завернули в кэш, но не учли, что в ключе не было текущей локали. Вышло так, что первым посетителем был кто-то из Чехии. Декоратор посчитал страницу для чешского языка и положил в кэш. В течение часа посетители видели страницу на чешском. Кнопку для экстренного сброса кэша никто не предусмотрел. И кстати — такая кнопка нужна, и она тоже требует затрат.
Резюмируя: лучший кэш — это его отсутствие. Если у вас нет кэшей в проекте — искренне поздравляю. Это сильно лучше той ситуации, когда кто-то его затянет.
-
Альтернатива для Германии
Немного политики. Пишут, что партия “Альтернатива для Германии” признана экстремистской — в смысле не в России, а в самой Германии.
Не имею понятия, что это за ребята, но — что-то мне это напоминает. Как только некая партия угрожает режиму, она объявляется экстремистской, то есть зашкварной. Нельзя не только быть в партии, но и репостить ее или поддерживать — это преследуется транзитивно.
Каждый европеец твердо знает: свобода и демократия есть только в Европе. И вот парни, которые чего-то достигли и мелькали в новостях, признаны экстремистами. Как-то не сходится: если они и правда клоуны, пусть наберут свои 0.1 процента и рассосутся. А если их поддерживает четверть Германии, это говорит о запросах общества, игнорировать которые — значит оттягивать неизбежное.
Но все режимы одинаковы. Главное — заткнуть рот несогласным и протянуть еще лет пять, а там видно будет.
UPD: в комментариях объяснили: этот тег дает право на прослушку и обыск без решения суда. Тоже ничего хорошего, кстати. Нужно только затем, чтобы создать базу для ареста.
-
Горький урок ABBYY
Этим утром попалась интересная статья: “Горький урок ABBYY: как лингвисты проиграли последнюю битву за NLP”.
Это история ABBYY: начало, быстрый рост, зенит, поражение на рынке с Гуглом, переезд и массовые увольнения. Разумеется, таких статей полно на Хабре, так что зачем еще одна?
Дело даже не в том, что написано в статье, а как. Автор работал в ABBYY, и у него лингвистические образование. Поэтому текст грамотный, последовательный, без тупых мемов и нейро-картинок для привлечения внимания. Нет слов-паразитов и штампов; нет воды, налитой чатом-гпт. Читать такой текст сегодня — удовольствие на уровне физического.
Содержание тоже не уступает: подробно рассказано, чем занимался ABBYY и какую Вавилонскую башню пытался построить. Интересны выводы, почему ABBYY с его ноу-хау и сотней лингвистов проиграл программистам из Гугла.
Мне показалось, выводы справедливы и в других областях, например программировании. Но об этом, возможно, в другой раз.
-
Три состояния
Упрощая, можно сказать, что человек бывает в трех состояниях: когда он один, когда с кем-то наедине и когда вокруг двое и более людей. В последнем случае неважно, сколько именно: два, три или сотня. Разница есть, но несущественная. Поэтому рассматриваю только варианты 0, 1 и 2+.
В каждом из состояний человек ведет себя по-разному. Фактически это три роли, между которыми мы часто переключаемся.
Понимание этих переключений помогает в жизни. Если человек хочет побыть один, не лезь с разговорами. Чтение книги и просмотр фильма относятся сюда же, потому что оба – про уход в себя.
Если человек в компании, не начинай важный разговор. При свидетелях человек думает прежде всего о том, как прикрыть свой зад и не опозориться перед другими. Здравый смысл отодвигается на второй план: репутация важнее.
Все сводится к тому, что любой разговор можно завалить, если начать его с человеком в неподходящем состоянии. С другой стороны – почти любой разговор можно завершить успехом, если собеседник в правильном состоянии. Осталось понять: где, когда и с кем, а остальное просто.
-
Про Обсидиан
Это заметка про Обсидиан, которая не понравится никому. Тем не менее…
В программе Обсидиан ничего плохого нет. Наоборот, это хорошая программа: у нее даже есть признаки адекватности. С ней можно начать без облачной учетки, хотя другая программа потребовала бы ее на старте и напоминала бы каждые пять минут: дорогой, ты не авторизован! Данные пропадут, скорей оформи подписку.
Меня смущает не программа Обсидиан, а ее пользователи. Здесь прямая аналогия с механическими клавиатурами. В них нет ничего плохого, но есть пользователи, которые годами крафтят кастомные клавиатуры и годами их обсуждают. Точно так же пользователи Обсидиана придают много внимание тому, что этого не стоит.
Обсидиан и аналоги создали некую моду: все толковые ребята ведут цифровые заметки, аналог Цеттелькассена. Если ты ведешь Обсидиан, ты профессионально растешь, ты крут, свой чувак. А если нет – фу-фу.
Эта мода сквозит в различных сообществах и Телеграм-каналах; об этом говорят в комментариях ко всем статьям на тему заметок. Пишут статьи о том, как из набора сервисов собрать свой “чемодан заметок”. В первом же комментарии упоминают Обсидиан, и начинается срач.
Обсидиан вывел в тренд некрасивую привычку: показывать всем свой граф знаний. Подобно владельцу айфона, который только что его купил и открывает без надобности, пользователь Обсидиана без конца смотрит на граф заметок. Искусственно наращивает его, добавляет лишние связи, потому что теория “чемодана” учит, что заметок без связей быть не должно. Стоит ли говорить, что этот граф – всего лишь ментальный онанизм.
Я не верю, что заметки и граф связей как-то помогают в обучении. Человек на полном серьезе пишет, что благодаря Обсидиану “выучил” Питон. То есть он прослушал лекции и для каждого урока составил заметки и связал их. Разумеется, попади он в первый проект на Питоне, эти заметки пойдут лесом. Важно не составлять заметки, а практиковать знания и закреплять их практикой. Скажем, прослушал урок про списки в Питоне – открываешь учебник по Турбо-Паскалю и прорешиваешь 20 задач, заменяя слово “массив” на “список”. От этого есть польза, а от цифровой заметки – нет.
То же самое пишет Барбара Оакли в книге “Думай как математик”, а также ее колеги-преподы. Все они утверждают, что конспектирование – это хорошо, но гораздо важнее вспоминать и пересказывать материал самому себе, а также скорее закрепить его практикой. Без этого конспектирование дает лишь видимость изучения и уподобляется подчеркиванию в книгах.
Я с трудом представляю, для чего люди ведут заметки в Обсидиане. Изучаешь ты, например, Питон, пишешь заметки к видеокурсу. Так заведи публичный канал в Телеграме и пиши туда! – пусть другие тоже видят, комментируют, поправляют. Всем польза. Для списка ссылок Обсидиан явно избыточен, подойдет файлик или скрытый канал в Телеграме. Для списка дел и дневника подходит обычный блокнот. Зачем брать какую-то программу?..
О том, что при работе с бумагой и ручкой мозг работает по-другому, я писал сотню раз и не буду повторять. Просто попробуйте.
В книге Make Time один из авторов приводит хороший пример. Он подсел на программу заметок, и после обновления операционки она отвалилась. Автор к тому времени забил и обновление не выпустил. Вряд ли это случится с Обсидианом, но тем не менее: не нужно становиться заложником софта.
Верный признак того, что Обсидиан избыточен – это обилие инструментов к нему. Фирма, которая его пишет, вынуждена тащить все подряд, чтобы удовлетворить всех. Помните, это как вкладки в браузере? Кому-то вертикальные, кому-то горизонтальные, по кругу, с предпросмотром, с попапом с данными о потребленной памяти, с закрытием по таймеру и так далее.
Не за горами час, когда вы обновите свой Обсидиан и обнаружите, что он стал медленным и появились интеграции с Твиттером, Покетом и бог знает чем – все потому, что об этом попросили упоротые клиенты, а менеджмент пошел на поводу. Если вам это ни о чем не говорит, погуглите историю сервиса Evernote.
Еще напомню про Агату Кристи, которая написала 50 томов без Обсидиана. Стивен Кинг пользовался блокнотом: первая электронная печатная машинка (даже не компьютер) появилась у него в зрелом возрасте. Чтобы дать что-то миру, вам не нужна программа электронных заметок. Все уже здесь.
Обсидиан сегодня – это мода. Это забавная программа вроде Майнкрафта, в которую не зазорно играть взрослым. Строишь свой мирок, наблюдаешь прогресс, делишься успехом с друзьями. Не возводите его в культ. Не будьте чуваком с картинки ниже. Это пройдет.
-
Программисты и бизнес
Иные говорящие головы двигают такой тезис: хороший программист по мере развития все больше приобщается к бизнесу. Думает, как улучшить продукт, повысить конверсии-воронки и все такое… ходит на совещания с владельцами бизнеса.
Ну… не знаю. Пытался играть в эти игры, но не смог. Мне не интересен бизнес. Не интересно, сколько пользователей привлекли, сколько уников зашло и так далее. Не мое это, хоть убейте.
Мне интересен код. Люблю делать задачу максимально просто: поменьше библиотек, меньше сервисов, без ОРМ. Пишу тесты, добиваюсь, чтобы все случаи покрывались локально, без облака. В общем-то и все. За это прошу лишь минимальную свободу и право выбора технологий.
И знаете, кажется, это то, чего от меня ждут. Заказчик хочет, чтобы задача была сделана быстро и качественно, и чтобы в будущем можно было легко поправить. Это я и делаю. Не имею понятия, кто и как пользуется моей работой, и бывает, узнаю об этом через год.
Я совершенно не сочувствую бизнесу, когда дела идут плохо. Это не мое дело. Я работал в настолько абсурдных стартапах, что искренне недоумевал, что в них кто-то верит. Их судьба предопределена, единственный вопрос — сколько раундов инвестиций они протянут. И еще: сколько опыта я унесу с собой после ухода.
Поэтому рассказы про лояльность бизнесу на меня не действуют. Я пишу качественный код и получаю деньги. Если код не принес прибыли — извините, тут помочь не могу.
Вряд ли я достойный пример для подражания, но рассказы про бизнес нужно воспринимать с прохладой. Это лапша.
-
Вертикальная полоса
Как-то сижу, быдлокодю понемножку и замечаю, что в редакторе включена вертикальная полоса – ограничитель 80 символов. Думаю: странно, не помню, что ее включал. Я таким не пользуюсь – переношу код согласно внутреннему чутью. Но пусть будет, тем более что лень искать, как выключить.
А через месяц я подвинул окно и обнаружил, что это не ограничитель, а вертикальный ряд битых пикселей. Представьте, он не двигается за редактором и искажает цвет в любой программе.
Может быть, в прошлом я бы устроил внутреннюю истерику: как так, 4к-монитор, 144 герц, и вдруг сдох целый ряд. Но потом я подвинул редактор на место и продолжил быдлокодить. С тех пор притворяюсь, что это ограничитель.
Чем меньше истерик – как внешних, так и внутренних – тем лучше для психики. Мне кажется, это правило продлевает жизнь, хоть и доказывать еще рано.
-
JSAM: a simple JSON writer and reader
JSam is a lightweight, zero-deps JSON parser and writer. Named after Jetstream Sam.
- Small: only 14 Java files with no extra libraries;
- Not the fastest one but is pretty good (see the chart below);
- Has got its own features, e.g. read and write multiple values;
- Flexible and extendable.
Installation
Requires Java version at least 17. Add a new dependency:
;; lein [com.github.igrishaev/jsam "0.1.0"] ;; deps com.github.igrishaev/jsam {:mvn/version "0.1.0"}
Import the library:
(ns org.some.project (:require [jsam.core :as jsam]))
Reading
To read a string:
(jsam/read-string "[42.3e-3, 123, \"hello\", true, false, null, {\"some\": \"map\"}]") [0.0423 123 "hello" true false nil {:some "map"}]
To read any kind of a source: a file, a URL, a socket, an input stream, a reader, etc:
(jsam/read "data.json") ;; a file named data.json (jsam/read (io/input-stream ...)) (jsam/read (io/reader ...))
Both functions accept an optional map of settings:
(jsam/read-string "..." {...}) (jsam/read (io/file ...) {...})
Here is a table of options that affect reading:
option default comment :read-buf-size
8k Size of a buffer to read :temp-buf-scale-factor
2 Scale factor for an innter buffer :temp-buf-size
255 Inner temp buffer initial size :parser-charset
UTF-8 Must be an instance of Charset
:arr-supplier
jsam.core/sup-arr-clj
An object to collect array values :obj-supplier
jsam.core/sup-obj-clj
An object to collect key-value pairs :bigdec?
false
Use BigDecimal when parsing numbers :fn-key
keyword
A function to process keys If you want keys to stay strings, and parse large numbers using
BigDecimal
to avoid infinite values, this is what you pass:(jsam/read-string "..." {:fn-key identity :bigdec? true})
We will discuss suppliers a bit later.
Writing
To dump data into a string, use
write-string
:(jsam/write-string {:hello "test" :a [1 nil 3 42.123]}) "{\"hello\":\"test\",\"a\":[1,null,3,42.123]}"
To write into a destination, which might be a file, an output stream, a writer, etc, use
write
:(jsam/write "data2.json" {:hello "test" :a [1 nil 3 42.123]}) ;; or (jsam/write (io/file ...)) ;; or (with-open [writer (io/writer ...)] (jsam/write writer {...}))
Both functions accept a map of options for writing:
option default comment :writer-charset
UTF-8 Must be an instance of Charset
:pretty?
false
Use indents and line breaks :pretty-indent
2 Indent growth for each level :multi-separator
\n
How to split multiple values This is how you pretty-print data:
(jsam/write "data3.json" {:hello "test" :a [1 {:foo [1 [42] 3]} 3 42.123]} {:pretty? true :pretty-indent 4})
This is what you’ll get (maybe needs some further adjustment):
{ "hello": "test", "a": [ 1, { "foo": [ 1, [ 42 ], 3 ] }, 3, 42.123 ] }
Handling Multiple Values
When you have 10.000.000 of rows of data to dump into JSON, a regular approach is not developer friendly. It leads to a single array with 10M items that you read into memory at once. Only few libraries provide facilities to read arrays lazily.
It’s much better to dump rows one by one into a stream and then read them one by one without saturating memory. Here is how you do it:
(jsam/write-multi "data4.json" (for [x (range 0 3)] {:x x}))
The second argument is a collection that might be lazy as well. The content of the file is:
{"x":0} {"x":1} {"x":2}
Now read it back:
(doseq [item (jsam/read-multi "data4.json")] (println item)) ;; {:x 0} ;; {:x 1} ;; {:x 2}
The
read-multi
function returns a lazy iterable object meaning it won’t read everything at once. Also, bothwrite-
andread-multi
functions are pretty-print friendly:;; write (jsam/write-multi "data5.json" (for [x (range 0 3)] {:x [x x x]}) {:pretty? true}) ;; read (doseq [item (jsam/read-multi "data5.json")] (println item)) ;; {:x [0 0 0]} ;; {:x [1 1 1]} ;; {:x [2 2 2]}
The content of the data5.json file:
{ "x": [ 0, 0, 0 ] } { "x": [ 1, 1, 1 ] } { "x": [ 2, 2, 2 ] }
Type Mapping and Extending
This chapter covers how to control type mapping between Clojure and JSON realms.
Writing is served using a protocol named
jsam.core/IJSON
with a single encidng method:(defprotocol IJSON (-encode [this writer]))
The default mapping is the following:
Clojure JSON Comment nil null String string Boolean bool Number number Ratio string e.g. (/ 3 2)
->"3/2"
Atom any gets deref
-edRef any gets deref
-edList array lazy seqs as well Map object keys coerced to strings Keyword string leading :
is trimmedAnything else gets encoded like a string using the
.toString
invocation under the hood:(extend-protocol IJSON ... Object (-encode [this ^JsonWriter writer] (.writeString writer (str this))) ...)
Here is how you override encoding. Imagine you have a special type
SneakyType
:(deftype SneakyType [a b c] ;; some protocols... jsam/IJSON (-encode [this writer] (jsam/-encode ["I used to be a SneakyType" a b c] writer)))
Test it:
(let [data1 {:foo (new SneakyType :a "b" 42)} string (jsam/write-string data1)] (jsam/read-string string)) ;; {:foo ["I used to be a SneakyType" "a" "b" 42]}
When reading the data, there is a way to specify how array and object values get collected. Options
:arr-supplier
and:obj-supplier
accept aSupplier
instance where theget
method returns instances ofIArrayBuilder
orIObjectBuilder
interfaces. Each interface knows how to add a value into a collection how to finalize it.Default implementations build Clojure persistent collections like
PersistentVector
orPersistenHashMap
. There is a couple of Java-specific suppliers that buildArrayList
andHashMap
, respectively. Here is how you use them:(jsam/read-string "[1, 2, 3]" {:arr-supplier jsam/sup-arr-java}) ;; [1 2 3] ;; java.util.ArrayList (jsam/read-string "{\"test\": 42}" {:obj-supplier jsam/sup-obj-java}) ;; {:test 42} ;; java.util.HashMap
Here are some crazy examples that allow to modify data while you build collections. For an array:
(let [arr-supplier (reify java.util.function.Supplier (get [this] (let [state (atom [])] (reify org.jsam.IArrayBuilder (conj [this el] (swap! state clojure.core/conj (* el 10))) (build [this] @state)))))] (jsam/read-string "[1, 2, 3]" {:arr-supplier arr-supplier})) ;; [10 20 30]
And for an object:
(let [obj-supplier (jsam/supplier (let [state (atom {})] (reify org.jsam.IObjectBuilder (assoc [this k v] (swap! state clojure.core/assoc k (* v 10))) (build [this] @state))))] (jsam/read-string "{\"test\": 1}" {:obj-supplier obj-supplier})) ;; {:test 10}
Benchmarks
Jsam doesn’t try to gain as much performance as possible; tuning JSON reading and writing is pretty challenging. But so far, the library is not as bad as you might think! It’s two times slower that Jsonista and slightly slower than Cheshire. But it’s times faster than data.json which is written in pure Clojure and thus is so slow.
The chart below renders my measures of reading a 100MB Json file. Then the data read from this file were dumped into a string. It’s pretty clear that Jsam is not the best nor the worst one in this competition. I’ll keep the question of performance for further work.
Measured on MacBook M3 Pro 36Gb.
Another benchmark made by Eugene Pakhomov. Reading:
size jsam mean data.json cheshire jsonista jsoniter charred 10 b 182 ns 302 ns 800 ns 230 ns 101 ns 485 ns 100 b 827 ns 1 µs 2 µs 1 µs 504 ns 1 µs 1 kb 5 µs 8 µs 9 µs 6 µs 3 µs 5 µs 10 kb 58 µs 108 µs 102 µs 58 µs 36 µs 59 µs 100 kb 573 µs 1 ms 968 µs 596 µs 379 µs 561 µs Writing:
size jsam mean data.json cheshire jsonista jsoniter charred 10 b 229 ns 491 ns 895 ns 185 ns 2 µs 326 ns 100 b 2 µs 3 µs 2 µs 540 ns 3 µs 351 ns 1 kb 14 µs 14 µs 8 µs 3 µs 8 µs 88 ns 10 kb 192 µs 165 µs 85 µs 29 µs 96 µs 10 µs 100 kb 2 ms 2 ms 827 µs 325 µs 881 µs 88 µs Measured on i7-9700K.
On Tests
One can be interested in how this library was tested. Although being considered as a simple format, JSON has got plenty of surprises. Jsam has tree sets of tests, namely:
- basic cases written by me;
- a large test suite borrowed from the Charred library. Many thanks to Chris Nuernberger who allowed me to use his code.
- an extra set of generative tests borrowed from the official
clojure.data.json
library developed by Clojure team.
These three, I believe, cover most of the cases. Should you face any weird behavior, please let me know.