Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

BeerJSON schema notes

How Werb handles BeerJSON 2.x — what it reads, what it writes, where it diverges, and why.

We vendor the schemas

The BeerJSON 2.x JSON Schema files live under vendor/beerjson/ as a git submodule pinning werb-dev/beerjson on the fix-packaging-vessel-graphics-items branch. Two upstream contributions are bundled there: a $ref + type: array packaging-vessel fix submitted as beerjson/beerjson#222, and a misc-additions root-level additionalProperties: false consistency fix. Once both land in beerjson/beerjson, we repoint the submodule at upstream master.

The vendoring keeps validation reproducible — every layer (the ajv validator in the PWA, the boon validator in the Rust tests, the CLI’s werb validate, the typify-generated werb-beerjson types) reads the same byte-for-byte schema set.

Strongly-typed model from schema

The werb-beerjson crate’s types are generated directly from the JSON Schema by typify. A schema bump becomes a Rust compile error at every site that constructs a now-invalid value — which is the whole point.

Regenerate after a schema update with pnpm gen:beerjson.

What Werb reads today

The single Import recipes button (and the CLI’s werb convert / werb validate) sniffs the file’s content and dispatches to the right parser. Supported on the way in:

FormatHow it shows upNotes
BeerJSON 2.x.beerjson (or .json with {"beerjson":{…}})Full schema; written back round-trip clean.
BeerXML 1.0.beerxml / .xmlRead-only via the WASM parser. Joliebulle v3 exports are BeerXML, so they land here.
Joliebulle v4.json with {"recipes":[…], "timestamp":…}Custom JSON shape the v4 desktop app emits when you export your library. Translated to BeerJSON 2.x via the same typed converter.

On the way out, Werb writes BeerJSON 2.x (round-trip clean) and BeerXML 1.0 (for sharing with tools that still need it).

Lossy by design

A few BeerXML fields have no place in BeerJSON 2.x (the recipe-level <IBU> value, hop addition notes, yeast addition temperature ranges) and are dropped on conversion. Their values are reconstructable from the recipe’s other content.

For joliebulle v4 specifically, the per-recipe id / nameId / pre-computed ratio fields are ignored — they’re useful to joliebulle internally but don’t translate.

Tolerated quirks (BeerXML)

Real-world BeerXML files don’t always follow the spec. Werb’s parser is deliberately lenient about the most common offenses, all of which show up in joliebulle v3 exports:

  • Empty self-closing enum elements (<TYPE />) on Recipe / Style / Yeast — treated as missing rather than as a parse error.
  • Missing <AMOUNT> on yeast (one pack = one item, defaults to 0).
  • <OG> / <FG> / <COLOR> at the recipe level instead of <EST_OG> / <EST_FG> / <EST_COLOR> — used as a fallback so the recipe’s gravity estimates aren’t silently lost.

Tolerated quirks (joliebulle v4)

The v4 JSON shape has its own conventions the parser papers over:

  • Numbers as strings ("alpha": "4", "step_temp": "67.0") — accepted either way.
  • Empty strings, "undefined", and "FALSE" as null markers — all collapse to None.
  • Fermentable / hop / misc weights in grams instead of BeerXML’s kilograms — divided by 1000 on the way in.
  • “Flame Out” hop use — mapped to BeerXML’s Aroma (post-boil late addition).

If you find a file Werb can’t import, please file an issue with the file attached.