8 points par GN⁺ 2025-09-22 | 1 commentaires | Partager sur WhatsApp
  • Sj.h est un parseur JSON ultra-léger d’environ 150 lignes, utilisable dans des environnements basés sur C99
  • Il se distingue par des allocations mémoire nulles et un état minimal, ce qui le rend adapté aux environnements embarqués légers ou aux projets qui veulent réduire les dépendances au minimum
  • Il adopte une approche de traitement direct pour le parsing des nombres et des chaînes, ce qui permet à l’utilisateur d’implémenter librement le comportement associé
  • Il prend en charge des messages d’erreur basés sur ligne:colonne, améliorant la lisibilité et l’identification de l’emplacement lors du debugging
  • Logiciel du domaine public, il autorise librement l’utilisation, la modification et la redistribution

Présentation du projet

  • Sj.h est un fichier d’en-tête C99 qui fournit des fonctions de parsing JSON avec un minimum de code, sans fonctionnalités excessives ni allocations mémoire inutiles
  • L’implémentation complète tient en environ 150 lignes, ce qui le rend adapté aux systèmes embarqués, aux outils, ou aux cas où l’on souhaite réduire la dépendance à des bibliothèques externes

Caractéristiques principales

  • Fonctionne avec zero-allocations et un minimal state, donc avec une empreinte mémoire très faible
  • Des messages d’erreur incluant les informations ligne:colonne permettent de repérer facilement l’emplacement des problèmes survenant pendant le parsing
  • Le parsing des nombres n’est pas fourni par défaut ; l’utilisateur peut employer la méthode de son choix, comme strtod, atoi
  • Le parsing des chaînes peut également être implémenté directement, y compris la gestion des Unicode surrogate pairs si nécessaire
  • Tout le code source est fourni dans le domaine public (Unlicense), ce qui permet son utilisation et sa modification sans contrainte de licence

Exemple d’utilisation

  • Un exemple simple est fourni pour parser une chaîne JSON dans une structure Rect
    char *json_text = "{ \"x\": 10, \"y\": 20, \"w\": 30, \"h\": 40 }";  
    typedef struct { int x, y, w, h; } Rect;  
    ...  
    
  • Il utilise une API simple et claire comme sj_Reader, sj_Value, sj_reader, sj_read, sj_iter_object
  • Le parsing des valeurs numériques ou la comparaison clé-valeur doivent être implémentés directement par l’utilisateur (pas de builtin fourni)
  • Le dossier demo permet de consulter divers exemples d’utilisation supplémentaires

Licence

  • Sj.h est un logiciel du domaine public que chacun peut utiliser sans restriction
  • Voir le fichier LICENSE pour plus de détails

Divers

  • Le code et l’arborescence des dossiers sont simples, ce qui permet une utilisation immédiate sans configuration ni procédure de build particulière
  • Il est autonome, sans dépendances externes, et convient surtout aux environnements où la légèreté et la facilité d’utilisation sont importantes

1 commentaires

 
GN⁺ 2025-09-22
Avis Hacker News
  • Ce que j’aime chez cet auteur, c’est que ses créations sont pour la plupart des bibliothèques en un seul fichier, écrites en ANSI C ou en Lua, avec un périmètre bien défini, une interface simple à utiliser et une excellente documentation ; j’apprécie aussi la licence de logiciel libre. En dehors de ce projet, il y a plusieurs de ses travaux que j’aime beaucoup : log.c (bibliothèque de logging C99 simple), microui (très petite bibliothèque d’interface utilisateur en mode immédiat), fe (petit langage embarquable), microtar (bibliothèque tar légère), cembed (outil pour embarquer des fichiers dans des en-têtes C), ini (bibliothèque légère pour fichiers de configuration .ini), json.lua (bibliothèque JSON légère pour Lua), lite (éditeur de texte léger en Lua), cmixer (mixeur audio portable pour jeux), uuid4 (petite bibliothèque uuid4 en C)
    • J’intègre log.c dans mes projets C à chaque fois ; je ne savais pas que l’auteur avait produit autant de bibliothèques. log.c est vraiment très facile à utiliser, je peux la recommander.
    • J’utilisais la bibliothèque Lume quand je faisais des jeux avec LOVE2D ; j’ai même croisé l’auteur plusieurs fois sur un salon IRC, et une fois je lui ai dit qu’une de ses idées était mauvaise. En y repensant, c’était en fait une bonne idée. https://github.com/rxi/lume vaut le détour.
    • C’est open source, mais ce n’est pas du vrai free software.
  • Cette bibliothèque ne vérifie pas les dépassements d’entiers signés aux lignes suivantes : L89, L149, L150 ; selon certaines valeurs d’entrée, cela peut provoquer un comportement indéfini
    • Certains développeurs apprécient la culture des bibliothèques C simples en un seul header ; des streamers comme Tsoding en sont de bons exemples. On sait que ces bibliothèques ne mettent pas l’accent sur la sécurité ou la richesse fonctionnelle. Tous les projets ne sont pas des logiciels de niveau entreprise exposés à des foules d’utilisateurs, donc cette approche peut aussi se défendre.
    • Il existe un bon article sur l’enflure des bibliothèques qui gèrent tous les cas limites, ainsi qu’une discussion connexe. Vouloir traiter toutes les erreurs fait exploser la complexité, et parfois ce n’est même pas du ressort de la bibliothèque.
    • Il faut une profondeur d’imbrication supérieure à 2 milliards, ou dans le second cas plus de 2 milliards de lignes, pour déclencher de l’UB. Si l’entrée JS est limitée à 1 GB, au-delà il y aura de toute façon des problèmes plus graves ailleurs dans la pile. Si je devais traiter du JSON de plus de 2 GB, je remplacerais tous les int du code source par du 64 bits ; cela dit, si l’entrée dépasse 2^64, ça finira quand même par casser. Je n’ai pas l’intention d’ajouter partout des vérifications de dépassement d’int.
    • Comme int est sur 32 bits, le problème n’apparaît que s’il faut des imbrications de 2 milliards de niveaux sur une ligne, un fichier de plus de 2 milliards de lignes, ou une ligne de plus de 2 milliards de caractères.
    • Je ne pense pas que cette bibliothèque soit utilisable en production.
  • Cette bibliothèque est assez permissive. Je ne dirais pas que cette approche est mauvaise, mais ceux qui l’utiliseraient sans lire le code doivent faire attention ; c’est aussi la raison principale pour laquelle le code est si petit. Avec l’exemple de démonstration du README, elle fonctionne ainsi :<br><code>{"x",10eee"y"22:5,{[:::,,}]"w"7"h"33<br>rect: { 10, 22, 7, 33 }</code>
    • Le parseur part généralement du principe que l’entrée est valide. La validation est un problème complètement distinct, que cette bibliothèque ne couvre pas. En fin de compte, elle sert à extraire les données.
    • Donc c’est faux ?
  • Je trouve que les bibliothèques de parsing JSON sont globalement un trou noir de souffrance : elles visent toutes des cas d’usage différents, sont pleines d’abstractions complexes, et souvent les deux à la fois. En réalité, si on implémente uniquement ce dont on a besoin pour un objectif précis, ce n’est pas un problème si difficile.
    • C’est étonnant de voir à quel point les bibliothèques JSON modernes deviennent complexes. La bibliothèque JSON C++ single-header de nlohmann, qui était autrefois vraiment simple, a maintenant<br>* 13 ans d’existence
  • des pull requests toujours fusionnées très activement
  • 122 millions de tests unitaires<br>et malgré ça, ils reconnaissent eux-mêmes que ce n’est pas la plus rapide. Si on veut de vraies performances, ils recommandent simdjson. Mieux vaut ne pas se lancer dans l’écriture d’une bibliothèque de parsing JSON soi-même : on peut en coder 90 % en 45 minutes, mais pour les 10 % restants il faut dix mille personnes et des milliers d’heures.
    • Il y a une ressource célèbre, Parsing JSON is a Minefield (2016) https://seriot.ch/projects/parsing_json.html
    • On voit rarement une conception aussi neutre que celle de cette bibliothèque : elle se contente d’itérer sur les clés et les éléments de tableau, puis de renvoyer des string slices distingués par type.
    • Ce projet met en avant le zero-allocation et un stockage d’état minimal, mais pour les types les plus courants comme les strings, il faut finalement quand même allouer. C’est une alternative très différente de ce qu’il me faut.
    • J’espère que les S-Expressions continueront à être appréciées.
  • C’est intéressant, mais je me demande combien de tests de conformité cette bibliothèque réussit https://github.com/nst/JSONTestSuite
    • Côté validation, cette bibliothèque ne semble pas très stricte. Par exemple, elle accepte aussi bien ] que } pour fermer un objet ou un tableau, et considère \v comme un whitespace, ce qui la rend plus permissive que le standard. Il vaut mieux la voir comme un « extracteur de données pour du JSON correct ». Mais écrire soi-même les parseurs de strings ou de nombres peut être pénible, surtout quand il faut un accord avec le producteur sur un sous-ensemble de la grammaire JSON.
    • Créer des tests de conformité à partir d’implémentations réelles serait vraiment utile : on pourrait générer un jeu de tests correspondant exactement au sous-ensemble/sur-ensemble accepté par une bibliothèque de parsing donnée. Cela éviterait les vulnérabilités dues au fait que deux parseurs interprètent différemment le même JSON, par exemple un contournement d’authentification.
    • C’est une vraie question : est-ce que les objets imbriqués sont pris en charge ?
  • Ça ressemble un peu à un demi-parseur : il ne convertit pas les nombres en float ou en int, donc il faudrait probablement ajouter soi-même cette partie. Cela dit, c’est impressionnant et j’aurais envie de l’utiliser ; je voulais juste le signaler.
  • Je me demande si le C99 précise que cette structure est initialisée à zéro par défaut, ou si le = { 0 } a simplement été omis. Dans ce code, r->depth pourrait ne pas être initialisé et finir par devenir accidentellement égal à depth dans la boucle sj__discard_until
    • r->depth est déjà initialisé à 0 ici, voir ce code
  • Je me demande à quoi sert ce genre de bibliothèque, alors qu’il semble déjà exister d’excellentes bibliothèques JSON. C’est un outil pédagogique ?
    • Elle s’intègre facilement dans une base de code existante, elle a une faible empreinte, n’utilise pas d’allocation sur le tas ni la stdlib (elle n’inclut que stdbool.h et stddef.h pour les définitions de types), il n’y a pas de gymnastique de templates C++, et l’API est intuitive. Les bibliothèques C qui cochent toutes ces cases sont vraiment rares ; en C++, c’est encore plus rare.
    • Le parsing sans overhead ni allocations est séduisant. C’est utile pour extraire seulement certaines propriétés de gros dumps JSON, comme les dumps Wikidata.
    • On peut l’utiliser directement sur un CPU embarqué ; à ce rythme, on pourra bientôt même faire tourner un serveur API sur une vape.
    • Comme le code est petit, il est plus facile à auditer pour les projets avec de fortes exigences de sécurité, et la conformité des licences est très simple aussi (aucune mention obligatoire).
    • Pour les débutants qui veulent s’en inspirer ou pour ceux qui cherchent à faire du parsing simple, cela peut constituer une bonne base de code. Dans de petits projets hobby avec des processeurs limités, une faible empreinte logicielle peut être utile ; même si, dans ce cas, je préférerais probablement un format comme TOML.
  • Sympa ! La prochaine fois que j’aurai besoin d’un parseur JSON en C, j’irai voir ça. J’utilise cJSON depuis des années avec satisfaction (https://github.com/DaveGamble/cJSON) ; avant cela, j’utilisais wjelement (https://github.com/netmail-open/wjelement), mais j’ai fini par changer à cause de quelques problèmes (je ne me souviens plus exactement lesquels, ça remonte).
  • J’ai l’impression que l’appeler un parseur est un peu forcé ; en général, cela ressemble davantage à un lexer.