js_option
A drop-in Option-like Rust type that distinguishes an explicit null from a missing/undefined JSON field.
Repository Health
Technical Analysis
js_option provides JsOption<T>, an enum shaped like Rust’s standard Option<T> but with three states instead of two: Some(value), Null, and Undefined. Where a regular Option collapses both an explicit JSON null and a missing field down to None, JsOption keeps that distinction intact through serde-based (de)serialization, which matters whenever a JSON API or PATCH-style payload needs to tell “clear this field” apart from “don’t touch this field.”
The crate is a single small type with an API deliberately modeled after Option’s own methods (unwrap, map, as_ref, as_deref, unwrap_or_else, and so on), so adopting it in an existing serde-based struct is close to a drop-in swap plus one #[serde(default, skip_serializing_if = "JsOption::is_undefined")] attribute.
What You Get
- A three-variant
JsOption<T>enum (Some,Null,Undefined) that plugs into any serde-derived struct field - Manual
Serialize/Deserializeimplementations (behind the default-onserdefeature) that round-tripnulland missing fields correctly with the right#[serde(...)]attributes - An
Option-mirroring method set —unwrap,unwrap_or,unwrap_or_else,unwrap_or_default,map,as_ref,as_mut,as_deref,as_deref_mut,is_some,is_null,is_undefined— for near-zero-friction adoption - Conversions to and from
Option<T>andOption<Option<T>>(from_option,from_implicit_option,into_option,into_nested_option) for interop with existing code
Common Use Cases
- Modeling JSON PATCH / partial-update request bodies where a client must distinguish “set field to null” from “leave field unchanged”
- Deserializing third-party JSON APIs (matrix/JS-originated schemas in particular, given the crate’s
rumaorigin) that intentionally distinguishnullfrom an absent key - Building typed Rust structs around JavaScript/TypeScript-shaped data where
nullandundefinedare semantically different - Any serde struct field that needs three-state optionality without hand-rolling a custom enum and its own
Serialize/Deserializeimpls
Under The Hood
Architecture
The crate is a single-file implementation: src/lib.rs defines the JsOption<T> enum and its inherent methods, with an optional src/serde.rs submodule (compiled only under the serde feature) supplying manual Deserialize/Serialize impls that delegate to Option<T>’s own serde support internally. There is no layering, no dependency injection, and no runtime data flow beyond enum construction and pattern matching — the entire public surface mirrors std::option::Option’s method names 1:1, so changing a variant would ripple predictably through every method body and through the small match in serde.rs, but the blast radius is trivial given the crate’s roughly 190 total lines.
Tech Stack
A pure Rust library crate targeting edition 2021 with MSRV 1.60.0, published to crates.io with zero mandatory dependencies. The one optional dependency, serde_core 1.0.221, is gated behind a serde feature that is enabled by default; dev-dependencies pull in the full serde crate with the derive feature to support the crate’s doctest example. There is no build tooling beyond standard cargo build/cargo test/cargo publish, no runtime, and no external services.
Code Quality
No dedicated tests/ directory or unit test files exist; the sole executable check is one doctest embedded in the crate-level /// documentation comment in src/lib.rs, exercised via cargo test --doc. #![warn(missing_docs)] is set at the crate root, and every public item carries a doc comment, backed by a .rustfmt.toml and a GitHub Actions workflow presumably running formatting/build checks. Error handling is minimal by design — the type has essentially one fallible path (unwrap() panicking on Null/Undefined) — but real test coverage beyond the single doctest is limited, which weighs against an otherwise clean, well-documented codebase.
API Design
The public API is deliberately built to shadow Option’s own method names (unwrap, unwrap_or, map, as_ref, as_deref, is_some), so a Rust developer already familiar with Option can adopt JsOption with almost no new API to learn. The crate-level doc comment ships a copy-pasteable example showing the exact #[serde(default, skip_serializing_if = "JsOption::is_undefined")] attribute combination needed for correct round-tripping — directly solving a well-known serde gotcha with one added dependency and a single attribute, no custom (de)serializer boilerplate required.