Open-source project
sheredom/json.h avatar
sheredom/json.h

json.h: a single header JSON parser for C and C++

🗄️ single header json parser for C and C++

843 stars98 forksCUnlicense

At a glance

What is it?
json.h parses a JSON document into a read-only DOM held in one allocation, with a flags bitset that can turn the parser into a JSON5 or simplified JSON reader. It is a parser only, and the DOM you get back is laid out for inspection rather than for mutation.
Who is it for?
Adopt json.h when you are writing C or C++ and need to read JSON without a build system, a package manager or a code generator, and when a single allocation for the whole document is worth more to you than a richer DOM API. Do not adopt it if you need to serialise JSON back to text, mutate a parsed document in place, or parse a stream incrementally, because the documented surface is parsing only and the result is explicitly read-only.
Can I use it commercially?
Yes. Unlicense is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 8 days ago.
What is it written in?
Mainly C, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.

Editorial analysis

One include, one allocation, a read-only DOM

The project is a single header file named json.h, and usage is one include line. There is no library to build and no dependency to fetch. The parser reads a UTF-8 JSON string and produces a Document Object Model, and the README states the result is read-only and lives in a single allocation buffer. That is the design decision everything else follows from. A single allocation means no per-node malloc traffic, no free list to walk, and a predictable teardown of one buffer, which is exactly what you want in a program with a fixed memory budget or on a platform where allocation is expensive. The trade-off is that the DOM layout is chosen for the parser, not for the consumer. The type tag lives inside the value struct, and the payload is a void pointer, so reading a field means checking the type and then casting. Convenient to parse, less convenient to work with over a long life.

json_parse is the short form, json_parse_ex is where the control lives

There are two entry points and they differ in how much you can say. The short one takes a buffer and its size:

c
struct json_value_s *json_parse(
    const void *src,
    size_t src_size);

The size is passed explicitly rather than inferred with strlen, so the parser can handle a buffer that is not NUL terminated, which matters when the JSON came from a network read or a memory mapped file. The extended form adds five more parameters:

c
struct json_value_s *json_parse_ex(
    const void *src,
    size_t src_size,
    size_t flags_bitset,
    void*(*alloc_func_ptr)(void *, size_t),
    void *user_data,
    struct json_parse_result_s *result);

Three of those are the interesting ones. flags_bitset selects extra parsing behaviour from an enum. alloc_func_ptr is a callback used for the single allocation, and the README notes that if it is NULL, malloc() is used, so you can supply a pool or arena and get the same one allocation discipline. user_data is passed as the first argument to that callback. The last parameter, result, is where an error type and the location in the source are reported, and it can be NULL, so you can skip error detail if you do not need it.

The flags bitset is a lenient-mode switchboard

The enum json_parse_flags_e defines the optional behaviours, each with a distinct bit:

c
enum json_parse_flags_e {
  json_parse_flags_default = 0,
  json_parse_flags_allow_trailing_comma = 0x1,
  json_parse_flags_allow_unquoted_keys = 0x2,
  json_parse_flags_allow_global_object = 0x4,
  json_parse_flags_allow_equals_in_object = 0x8,
  json_parse_flags_allow_no_commas = 0x10,
  json_parse_flags_allow_c_style_comments = 0x20,
  json_parse_flags_deprecated = 0x40,
  json_parse_flags_allow_location_information = 0x80,
  json_parse_flags_allow_single_quoted_strings = 0x100,
  json_parse_flags_allow_hexadecimal_numbers = 0x200,
  json_parse_flags_allow_leading_plus_sign = 0x400,
  json_parse_flags_allow_leading_or_trailing_decimal_point = 0x800,
  json_parse_flags_allow_inf_and_nan = 0x1000,

The individual flags cover trailing commas in objects and arrays, unquoted object keys, a global unbracketed object, using equals as well as colon between key and value, dropping comma separators entirely, C style comments in either the // or /* */ form, single quoted strings, hexadecimal numbers written as 0x42, a leading plus sign, a leading or trailing decimal point, the Infinity and NaN identifiers, and strings that span multiple lines. Two composite values sit on top: json_parse_flags_allow_simplified_json enables the first five of those, and json_parse_flags_allow_json5 enables a wider set. The interesting one for a real project is json_parse_flags_allow_c_style_comments, because a JSON file with comments is a common configuration format and strict parsers reject it. Note also json_parse_flags_deprecated, a reserved bit that the README describes only as a deprecated option.

Location tracking is opt-in and it inflates the DOM

One flag changes what the DOM looks like rather than what the parser accepts. With json_parse_flags_allow_location_information enabled, the parser tracks where each value sits in the input, and the README says this is useful for alerting users to errors with precise location information pertaining to the original source. The mechanism is a set of casts rather than a separate type hierarchy. All json_value_s pointers can be cast to json_value_ex_s, and the json_string_s name member of a json_object_element_s can be cast to json_string_ex_s, which is where the location is read from. The cost is stated plainly: the option increases the memory budget required for the DOM used to record the JSON. That is a reasonable trade for a linter, an editor plugin or a configuration validator, where a message that says line 42 rather than a generic parse failure is worth the extra bytes. It is a bad trade for a service parsing thousands of documents per second. Since the extra structures are reached by casting, the type of the pointer does not change at compile time, which means the extra cost is invisible to the type checker and only appears in your memory budget.

Reading the DOM means checking a type tag, then casting

The value struct is deliberately minimal:

c
struct json_value_s {
  void *payload;
  size_t type;
};

The payload points at the contents of the value, and the type says which struct payload points to, one of the values of json_type_e. The README calls out a detail that will bite you if you miss it: if the type is json_type_true, json_type_false or json_type_null, the payload will be NULL. So the pointer is not always a pointer to something, and a generic accessor that dereferences payload without checking the type will crash on those three. The usage example shows the intended pattern, checking the type before casting the payload to the object struct:

c
const char json[] = "{\"a\" : true, \"b\" : [false, null, \"foo\"]}";
struct json_value_s* root = json_parse(json, strlen(json));
assert(root->type == json_type_object);

The example works on the JSON string {"a" : true, "b" : [false, null, "foo"]}. Because the root is a tagged union by hand, walking the document means a chain of type checks, and there is no way to ask the library for a value by path, no accessor helpers and no iteration helpers in the documented surface. You are expected to know the shape of the DOM structs and cast them yourself.

A parser with no writer and no incremental input

The clearest limitation is visible in what the documentation does not cover. Every documented entry point parses. There is no function for serialising a value back to a JSON string, and no function for building a value in memory. There is also no streaming or incremental parser, since the input is a complete buffer with a known size and the output is a complete DOM, so a document that arrives in pieces over a socket has to be buffered before it can be handed to json_parse. For a program that only reads configuration or consumes an API response, that is fine and even desirable. For a program that has to produce JSON, or that receives JSON in chunks, this library is the wrong shape, and the alternative is a different library or a hand written writer. There is also no thread safety statement and no reentrancy claim in the documentation, so a parser that keeps no global state is safe to assume nothing about beyond what the code shows.

Compilers, platforms and the Unlicense

The supported matrix is stated at the top of the README. The current supported compilers are gcc, clang and msvc, and the current supported platforms are Windows, mac OS and Linux. The build status badge points at a CMake workflow, so the CI builds through CMake even though the library itself is a header, and the repository layout is correspondingly small: json.h at the top level next to README.md, LICENSE and a test directory, with the workflow configuration under .github/. There is no package manifest, no Makefile and no dependency list, because there is nothing to depend on. The licence is the Unlicense, which places the code in the public domain and disclaims liability, and it is one of the most permissive licences available, with no attribution requirement on use. The repository is not archived and the last push was on 2026-10-15, so the file is being touched. The release list is thin, with a single tag named pre-ai dated 2026-05-22, so in practice you copy json.h into your tree and track it there rather than consuming a versioned artefact.

Editorial conclusion

Adopt json.h when you are writing C or C++ and need to read JSON without a build system, a package manager or a code generator, and when a single allocation for the whole document is worth more to you than a richer DOM API. Do not adopt it if you need to serialise JSON back to text, mutate a parsed document in place, or parse a stream incrementally, because the documented surface is parsing only and the result is explicitly read-only. Before you commit, copy json.h into your project, decide whether you need json_parse_flags_allow_location_information given it raises the memory budget, and confirm the allocator callback semantics of json_parse_ex if you intend to supply your own allocator.

Frequently asked questions

How do I use json.h in a C project?

Copy the json.h header into your project and include it in your source. The parser is invoked with json_parse, which takes a UTF-8 buffer and its size in bytes and returns a pointer to the root of the parsed DOM.

What is the difference between json_parse and json_parse_ex?

json_parse takes only the source buffer and its size. json_parse_ex additionally takes a flags bitset, an allocation callback with user data, and a result structure that reports the error type and where in the source it occurred.

Can json.h parse JSON5 or comments?

Yes, through the flags bitset. json_parse_flags_allow_json5 enables a set of JSON5 behaviours, json_parse_flags_allow_simplified_json enables another set, and json_parse_flags_allow_c_style_comments lets the parser ignore // and /* */ comments in the input.

What licence is json.h released under?

The Unlicense, which places the code in the public domain. The repository is not archived and the last push was on 2026-10-15.

Official sources

  1. Issues
  2. License: Unlicense
  3. README
  4. Releases
  5. sheredom/json.h on GitHub
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/sheredom-json-h.svg)](https://hysenlabs.com/projects/sheredom-json-h)

Was this useful?