Skip to the content.

Migrating from V2 to V3

V3 is a full rewrite. V2 and V3 share no API surface. The concepts map across, but every type name, every function call, and the build model itself changed.

Build model

V2 was a two-file library. You compiled json.c and linked it.

# V2
cc main.c json.c -I. -o app

V3 is a single header. There is nothing to compile separately.

# V3
cc main.c -I. -o app

Type names

V2 V3
json_element_t json_value
json_object_t json_object (inside json_value.as.object)
json_array_t json_array (inside json_value.as.array)
json_entry_t json_member
json_number_t json_number (inside json_value.as.number)
json_string_t (typedef for const char *) json_string (struct with .data + .len)
json_boolean_t int
json_error_t json_status (enum) + json_error (struct with byte offset)
json_element_type_t json_type

Parsing

V2 returned a result monad that had to be unwrapped:

/* V2 */
result(json_element) res = json_parse(text);
if (result_is_err(json_element)(&res)) {
    json_error_t err = result_unwrap_err(json_element)(&res);
    /* err is an enum, no byte offset */
}
json_element_t root = result_unwrap(json_element)(&res);
/* must manually free later */

V3 writes into a caller-owned json_root and returns a status code:

/* V3 */
json_root root;
json_error err;   /* includes a byte offset */
if (json_parse(text, &root, &err) != JSON_OK) {
    fprintf(stderr, "error at byte %lu\n", (unsigned long)err.offset);
}
/* root owns everything; json_free releases it all */

Object lookup

V2 stored object members in a hash map and looked them up with json_object_find:

/* V2 */
result(json_element) res = json_object_find(obj, "key");
json_element_t val = result_unwrap(json_element)(&res);

V3 stores members in insertion order (iterable by index) and looks up by key with json_object_get. Duplicate keys are preserved; the last one wins on lookup:

/* V3 */
const json_value *val = json_object_get(&root.value, "key");

Number access

V2 numbers carried a type tag — a value was either a long or a double, never both:

/* V2 */
if (elem.value.as_number.type == JSON_NUMBER_TYPE_LONG)
    long v = elem.value.as_number.value.as_long;
else
    double v = elem.value.as_number.value.as_double;

V3 always computes both. as_double is always valid. as_long is valid when is_long is true (integer that fits in long). Overflow is not an error:

/* V3 */
double d;
long   l;
int    fits;
json_number_get(val, NULL, NULL, &d, &l, &fits);
/* or the typed shortcuts: */
json_number_get_double(val, &d);
json_number_get_long(val, &l);   /* JSON_WRONG_TYPE if overflows */

Memory

V2 required manual element-by-element cleanup. V3 frees the entire tree in one call:

/* V3 */
json_free(&root);

What V3 adds that V2 never had