Skip to content

v1.0.0 — Stable wire format

Latest

Choose a tag to compare

@JordanMarr JordanMarr released this 02 Nov 02:13

v1.0.0 — Stable wire format 🎉

Serde.FS leaves beta. The JSON wire format is now a documented contract: every shape the .NET codecs and the Fable client write is listed in the README, and 1.x will read what any 1.x wrote. Any change that would break that waits for 2.0.

📖 README → Wire format

⚠️ Upgrading from beta

  • Upgrade all Serde.FS packages together, and redeploy client and server as a unit. A few wire shapes changed (below), so a beta client and a 1.0 server can disagree. A browser tab still running the old client can hit errors until it reloads.
  • JSON you stored with beta.6 should still decode. The decoders still accept the old shapes where the format changed (int64 / decimal as numbers, null for a nested option).
  • Removed attributes: [<SerdeSerialize>], [<SerdeDeserialize>], [<SerdeSkipSerialize>] and [<SerdeSkipDeserialize>]. They were read by the generator and then ignored, so no generated code changes: replace the first two with [<Serde>] and delete the skip attributes. [<SerdeSkip>] on a record field is now a build error (it never skipped anything); it still works on union cases and enum cases.

Wire format changes

  • int64, uint64 and decimal are JSON strings ("9223372036854775807"). JavaScript reads JSON numbers as doubles, so the Fable client silently lost precision past 2^53. A JSON number is still accepted on decode.
  • Some None survives. For a nested option or unit option, Some is written as {"Some": ...}; Some None used to collapse to None. Single options are unchanged: Some 5 is 5, None is null.
  • Null byte[], arrays and seqs from interop are written as null instead of throwing, and Some null is sent as None, the same rule as strings.
  • New codecs: sbyte, int16, byte, uint16, uint32, uint64, float32, char and TimeSpan (.NET constant format, "1.02:03:04.5000000"). Fields of these types used to fail at runtime.

Stricter decoding

The rule for 1.0: when in doubt, reject. A decoder can be loosened later without breaking anyone; tightening it would break.

  • Integers reject fractional (1.9) and out-of-range values instead of truncating or wrapping them. NaN and infinity are rejected.
  • Dates and times decode from ISO 8601 only. Culture-shaped text like "01/02/2026" is no longer accepted.
  • Guid decodes only the standard hyphenated form.
  • A union's Fields and a tuple must hold exactly as many values as the type has; extra values used to be ignored.

Clearer decode errors

.NET and the Fable client now fail with the same messages, naming the type and field:

Missing field "Zip" for Shop.Address
Union case Shop.Shape.Rectangle expects 2 field(s), got 1
Unknown case "Hexagon" for Shop.Shape
Expected a JSON object for Shop.Address, got an array

These replace a bare KeyNotFoundException / IndexOutOfRangeException on .NET, and on Fable an undefined value that surfaced later.

Fixes

  • Culture independence: numbers were written with the current culture (1,5 under de-DE) and dates parsed with it. Both now use the invariant culture.
  • float precision: floats were encoded through decimal, which keeps 15 significant digits and fails outside its range. They are now written in the shortest form that round-trips.
  • Fable client dates: DateTime kind and DateTimeOffset offset are preserved; a UTC value no longer shifts by the browser's offset when sent back.
  • Fable client float32 is rounded to single precision on decode.
  • Qualified primitives (System.Guid, System.TimeSpan) in an option or tuple no longer break the build.
  • Named RPC parameters (abstract Probe: from: DateTime * until: DateTime -> ...) no longer generate obj (#16).
  • Fable generator on Linux: backslash ProjectReference paths and paths containing uppercase letters work on case-sensitive file systems (#17).

Tested in both directions

  • A new round-trip suite sends the same values through the .NET codecs and through the generated Fable client under Node. Each side decodes what the other wrote. It covers:
    • every number type at its limits;
    • unicode and escapes;
    • all date and time types;
    • nested options, collections, Maps with non-string keys, unions, renames and bare RPC values.
  • A Linux CI job runs the tests, the sample apps, the round-trip suite and the Native AOT / trimming gate on every push.

Full changelog: v1.0.0-beta.6...v1.0.0