|
json-gen-c
0.1.5
json-gen-c generate C code for json manipulation
|
This document describes how to safely evolve .json-gen-c schemas over time without breaking existing serialized data or downstream consumers.
As applications grow, their data schemas inevitably change: new fields are added, old fields become irrelevant, and types may need adjustment. Schema evolution is the practice of making these changes in a controlled way so that old data can still be read by new code (forward compatibility) and new data can still be read by old code (backward compatibility).
json-gen-c's generated unmarshal functions silently skip unknown JSON keys. This means:
_init() default value.When new code produces JSON with new fields, old code (generated from an earlier schema version) simply ignores the unknown keys. This is also automatic.
Caveat: if old code re-marshals the struct, the unknown fields will be lost (they were never stored). This is acceptable for most use cases but important to understand for data pipeline scenarios.
These changes are backward- and forward-compatible — they will not break existing consumers:
| Change | Why It's Safe |
|---|---|
| Add a new field | Old data is parsed normally; the new field gets its init default. Old code ignores the new key. |
Add a new optional field | Same as above, plus has_<field> will be false for old data. |
Add a new nullable field | Same as above. |
| Add a new enum value | Old code that encounters the unknown string will fail unmarshal (see caveats). |
| Add a new oneof variant | Old code ignores the unknown tag value. |
Mark a field @deprecated | No wire-format change. Downstream C code gets compiler deprecation warnings. |
| Add a default value to a field | Only affects _init(); no wire-format change. |
Rename a field's JSON key with @json | If the old JSON key remains via alias, this is safe. |
These changes will break existing consumers and should be avoided or carefully managed:
| Change | Why It Breaks |
|---|---|
| Remove a field | Old JSON containing the removed key is silently ignored (safe for unmarshal), but code that accesses the field will fail to compile. |
| Change a field's type | Unmarshal will produce incorrect values or fail. |
Rename a field without @json alias | Old JSON with the previous key is ignored; the renamed field gets its default. |
| Remove an enum value | Code referencing the removed constant fails to compile. Old JSON with the removed string fails unmarshal. |
| Remove a oneof variant | Code referencing the removed variant fails to compile. |
| Change a field from non-array to array (or vice versa) | Wire format mismatch. |
| Change array from fixed to dynamic (or vice versa) | Generated struct layout changes. |
Use @json to keep the old wire-format name while changing the C identifier:
Old JSON {"name": "Alice"} still unmarshals correctly into display_name.
Mark it @deprecated instead of deleting it:
The field remains fully functional in marshal/unmarshal, but any C code accessing obj->timeout_sec will trigger a compiler deprecation warning.
Make the new field optional so that old data (which lacks it) still parses:
Check obj->has_correlation_id before using the field.
Changing int to long or float to double changes the generated struct layout. Instead, add a new field and deprecate the old one:
Application code can check has_value_v2 and fall back to value for old data.
@deprecated can be placed before a field declaration, enum value, or oneof variant:
__attribute__((deprecated("message")))__declspec(deprecated("message"))__attribute__((deprecated)) on GCC/Clang. MSVC does not support per-enumerator deprecation.For a schema with @deprecated int old_count;, the generated header contains:
The JGENC_DEPRECATED macro is defined in the generated header and can be overridden by defining it before including the generated header.
optional when backward compatibility matters. This ensures old data without the field still parses correctly.@json aliases when renaming to maintain wire-format compatibility.@deprecated in one release, then remove them in a later release after consumers have migrated.@deprecated annotations to help future maintainers understand when and why a field was deprecated.--check-compat to automatically detect breaking changes between schema versions before deploying.