Writing a Workalike¶
So you want a JSON Pointer library in another language that behaves like kPointer. The conformance suite is your acceptance test and the language-neutral spec. This page is the part the fixtures cannot give you: the translator's note — which layers to copy verbatim, which to make idiomatic, and the handful of Kotlin idioms in the reference that need a conscious decision in your target language.
It is short on purpose, and aimed squarely at you, six months from now, starting the port.
What to copy verbatim, what to make idiomatic¶
kPointer has two kinds of code, and they port differently.
Copy the syntax and algorithm logic verbatim. Pointer parsing, escaping, the relative-pointer
grammar, resolve, and mutate are pure string-and-tree algorithms with no host dependency. Their
behavior is normative and fully pinned by syntax/* and algorithm/*. Translate them line for line;
resist "improving" them. Every deviation is a conformance risk.
Make the adapter layer idiomatic. The KpaStruct / KpaList / KpaPrimitive abstraction
exists to bridge kPointer to some host tree type. In your language the natural bridge may look
completely different — a trait, a protocol, a type class, a visitor, a tagged union you already have.
Do not transliterate the Kotlin interface hierarchy; re-express the same contract (read a key,
read an index, absent-vs-error, rebuild on mutate) in whatever your host considers idiomatic. The
conformance fixtures test the contract, not the interface shape.
The dividing line: if a fixture pins it, copy it; if only the reference's internal structure implies it, redesign it.
Kotlin idioms that need a conscious mapping¶
Sealed interface → discriminated union¶
KpaElement is a sealed interface with exactly three cases (KpaStruct, KpaList,
KpaPrimitive); RelativePointerResult is sealed with Pointer / Index / Key; the error
taxonomies are sealed families. "Sealed" means closed set, exhaustively matchable.
Map each sealed hierarchy to your language's closed sum type:
- Rust / Swift →
enumwith associated data. - TypeScript → a discriminated union with a
type/kindtag (the fixtures already use exactly this shape for elements). - A language without sum types → an abstract base plus a
kinddiscriminant, and amatch/switchthat a reviewer can see is exhaustive.
The payoff is the same as in Kotlin: resolve and mutate are written as exhaustive matches over the element kind, and adding a case is a compile-time prompt to handle it everywhere.
Poko / structural equality → explicit deep compare¶
The reference relies on structural value equality. KPointer and friends are
Poko types (kPointer's stand-in for data class), so ==
compares by content, and the fixture element/document comparisons are deep structural matches
(see element matching).
If your language's == is reference equality by default (Java without records, JavaScript
objects, many others), you must write the deep compare yourself and route every "does this element
equal that one" through it. A shallow or reference compare will pass the trivial fixtures and fail the
nested-struct and nested-list cases in confusing ways. Decide your equality strategy before you
implement resolve/mutate, not after.
Numeric and character decisions¶
Two decisions the fixtures depend on, both easy to get subtly wrong:
longversusdoubleare distinct. The element type system tags integers (long) and floating-point (double) separately, precisely because JSON — and single-numeric-type languages like JavaScript — blur them. Preserve the distinction end to end: alongfixture must not round-trip as adouble. If your host has one number type, carry the tag alongside the value.- Parse digits as ASCII only. Segment-index and relative-pointer integers are parsed strictly as
ASCII
0–9. Do not use a locale-aware or Unicode-digit parser: strings containing non-ASCII digits (e.g. Arabic-Indic or full-width numerals) must be rejected, not silently accepted. The reference restricts this deliberately, anderror-relative-non-ascii-digitpins it. Many standard-library integer parsers accept Unicode digits by default — check yours, and constrain it.
Relatedly, leading-zero and empty-integer rules ("01" rejected, no-leading-digit rejected) are part
of the grammar, not the number parser — enforce them in the grammar as the reference does, so they
hold regardless of how permissive your integer parser is.
Suggested porting order¶
- Pointer syntax (
syntax/parsing.json) — parse/escape/emit for RFC 6901, fragment, and (optionally) dot notation. Get the escaping edge cases right here. - Relative pointers (
syntax/relative-apply.json,syntax/relative-compute.json) — the grammar,#queries, and index adjustments. - Resolve (
algorithm/resolve.json) — needs your idiomatic tree abstraction and the absent-vs-error rule. - Mutate (
algorithm/mutate.json) — non-destructive set/remove, rebuilding containers.
Wire each fixture file into your test framework as you go, keyed on the fixture name. When all five
files are green you have a faithful workalike. The coverage matrix
maps every RFC 6901 edge case to the fixture that covers it.