Skip to content

specta: emit Feature.properties via named JsonValue alias#8

Merged
johncarmack1984 merged 2 commits into
mainfrom
john/specta-json-value-alias
May 6, 2026
Merged

specta: emit Feature.properties via named JsonValue alias#8
johncarmack1984 merged 2 commits into
mainfrom
john/specta-json-value-alias

Conversation

@johncarmack1984
Copy link
Copy Markdown
Collaborator

Summary

Two specta-rc.24 typegen workarounds that downstream consumers
(specifically fltsci/fltsci) depend on:

  • geojson::specta_compat::JsonValue -- a named TS recursive
    alias for serde_json::Value. specta-rc.24's default impl is
    inline: true AND recursive (Vec<Value>, Map<String, Value>),
    so the export pass filters the type out (inline: true makes
    requires_reference return false) and consumers see references to
    an undefined Value name. The new type emits as

    export type JsonValue =
        | null
        | boolean
        | number
        | string
        | JsonValue[]
        | { [key: string]: JsonValue };

    via init_with_sentinel + specta_typescript::define(). The Rust
    type is a placeholder (never instantiated) and only used through
    #[specta(type = ::geojson::specta_compat::JsonValue)] overrides.
    Feature.properties is wired up here; downstreams can use the
    same type for any of their own serde_json::Value fields.

  • Id::Number flatten -- serde_json::Number's rc.24 Type impl
    emits a tagged { f64 } | { i64 } | { u64 } union even though the
    wire shape is just a JSON number. #[specta(type = f64)] on the
    Number variant restores the flat string | number TS shape that
    matches Id's #[serde(untagged)] runtime behavior.

Runtime serde behavior is unchanged on both fronts -- only the
typegen view is affected. Both workarounds are scoped so they can
peel back when specta-rc.24 (or its replacement) fixes the
underlying type-graph emission.

Test plan

  • cargo check --features specta clean
  • downstream fltsci/fltsci PR #3145 typecheck/lint/format all
    green against this rev

Workaround for specta-rc.24's broken `serde_json::Value` Type impl,
which is `inline: true` and recursive (`Vec<Value>`, `Map<String,
Value>`). Inline + recursive means the export pass filters the type
out (since `inline: true` makes `requires_reference` return false),
so consumers see references to an undefined `Value` name in TS.

Add `geojson::specta_compat::JsonValue` -- a manual `specta::Type`
that emits a top-level recursive alias:

  export type JsonValue =
    | null
    | boolean
    | number
    | string
    | JsonValue[]
    | { [key: string]: JsonValue };

Override Feature.properties (and its DeserializeFeatureHelper
counterpart) to use it. Runtime serde behavior is unchanged.

External downstream crates can use the same type to override their
own `serde_json::Value` fields, since the recursive-inline bug
affects every consumer, not just geojson.
specta-rc.24's `serde_json::Number` Type impl emits a tagged union
of `{ f64 } | { i64 } | { u64 }` even though the wire shape is just
a JSON number. Override at the variant level so untagged `Id` lands
as `string | number` in TS, matching its actual runtime shape.

Runtime serde behavior is unchanged.
@johncarmack1984 johncarmack1984 merged commit ec26ae1 into main May 6, 2026
4 checks passed
@johncarmack1984 johncarmack1984 deleted the john/specta-json-value-alias branch May 6, 2026 02:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant