nojson
Use this skill when integrating nojson into Rust code. Focus on the crate's
actual API shape and usage patterns, not generic JSON background.
What this crate exposes
Json<T>— wrapper givingFromStr/Displayfor anyTthat implements the corresponding nojson traitsDisplayJson— JSON analogue ofcore::fmt::DisplayRawJson<'text>/RawJsonOwned— a parsed, validated JSON text that has not been converted to Rust types yetRawJsonValue<'text, 'raw>— a node inside aRawJson; the entry point for reading valuesRawJsonMember<'text, 'raw, 'a>— result ofto_member; resolves withrequired()/optional()/TryInto<Option<T>>JsonFormatter— the formatter passed toDisplayJson::fmtand to thenojson::json(|f| …)closureJsonArrayFormatter/JsonObjectFormatter— supplied insidef.array(|f| …)/f.object(|f| …)(and via thenojson::array/nojson::objectshortcuts) for adding elements / members- Free functions
json(),object(),array()— return values thatimpl Display + DisplayJson JsonValueKind,JsonParseError
Choosing the right API
- Typed round-trip — use
Json<T>withT: DisplayJson + for<'t, 'r> TryFrom<RawJsonValue<'t, 'r>, Error = JsonParseError>. Parse viatext.parse::<Json<T>>(), format viaJson(&value).to_string(). - Inline output (incl. pretty-printing) — use
json(),object(), orarray()with a closure. Good for one-off logging / CLI output without defining a struct. - Imperative navigation / validation —
RawJson::parse(text)?, then traverse withRawJsonValuemethods. Use this when you need positions, conditional fields, or rich error context. - Owned JSON detached from the input lifetime — use
RawJsonOwnedorRawJsonValue::extract().into_owned(). - JSON with comments / trailing commas —
RawJson::parse_jsonc/RawJsonOwned::parse_jsonc. Returns(RawJson, Vec<Range<usize>>)where the ranges are comment byte spans in the original text.
Usage gotchas
- The crate is
#![no_std]+alloc. The defaultstdfeature enablesDisplayJson/TryFromimpls forHashMap,HashSet,PathBuf,IpAddr,SocketAddr, etc. Turn it off forno_stdbuilds. - Custom parsing implements
TryFrom<RawJsonValue<'text, 'raw>>withError = JsonParseError— notFromStr.FromStronJson<T>delegates to thisTryFromimpl. to_member(name)does a linear scan. If you read many siblings, preferto_object()once and match keys yourself.to_path_member(&["a","b","c"])is a convenience. Intermediate keys are required; only the last may be handled as optional.as_string_str()returnsErrwhen the JSON string has escapes; it never allocates. For general decoding useto_unquoted_string_str()— it returnsCow<'text, str>(borrowed when no escapes).f32/f64that are not finite (NaN, ±Infinity) serialize asnull. JSON has no NaN literal; do not expect round-trips through floats.()serializes to / deserializes fromnull.Option<T>::Nonealso maps tonull.- Pretty-printing is controlled on the formatter:
f.set_indent_size(n)+f.set_spacing(true). Settings apply to the current depth and deeper; innernojson::json(|f| …)can override locally. RawJsonValueisCopy; most traversal methods consumeselfby value on purpose — pass it around freely.RawJsonValue::index()is stable within oneRawJson. Cache it and re-fetch withget_value_by_indexfor O(1) access after validation.
API patterns to preserve
Typed round-trip for a custom struct:
struct Person { name: String, age: u32 }
impl nojson::DisplayJson for Person {
fn fmt(&self, f: &mut nojson::JsonFormatter<'_, '_>) -> std::fmt::Result {
f.object(|f| {
f.member("name", &self.name)?;
f.member("age", self.age)
})
}
}
impl<'text, 'raw> TryFrom<nojson::RawJsonValue<'text, 'raw>> for Person {
type Error = nojson::JsonParseError;
fn try_from(v: nojson::RawJsonValue<'text, 'raw>) -> Result<Self, Self::Error> {
Ok(Person {
name: v.to_member("name")?.required()?.try_into()?,
age: v.to_member("age")?.required()?.try_into()?,
})
}
}
let p: nojson::Json<Person> = r#"{"name":"a","age":1}"#.parse()?;
let s = nojson::Json(&p.0).to_string();
Inline JSON object (most common shortcut):
// `nojson::object(|f| ...)` is shorthand for `nojson::json(|f| f.object(...))`.
// Reach for it whenever you want a one-off object without defining a struct.
let req = nojson::object(|f| {
f.member("user", "alice")?;
f.member("ids", &[1, 2, 3])?; // arrays go through the blanket impl below
f.member("active", true)
}).to_string();
nojson::array(|f| ...) exists for symmetry, but is rarely needed: anything
that already implements DisplayJson (&[T], Vec<T>, [T; N], iterators
collected into these, etc.) can be passed directly to f.member / f.value,
so an array literal usually suffices.
Inline pretty-printed output (when you need indent / spacing):
// Use `nojson::json` (not `object`) when you have to call `set_indent_size` /
// `set_spacing` — those live on `JsonFormatter`, not `JsonObjectFormatter`.
let pretty = nojson::json(|f| {
f.set_indent_size(2);
f.set_spacing(true);
f.object(|f| {
f.member("items", &[1, 2, 3])?;
f.member("enabled", true)
})
});
let text = pretty.to_string();
Optional members:
let obj = json.value();
// `try_into()` on the `RawJsonMember` goes through the blanket
// `Option<T>: TryFrom<RawJsonMember>` impl — use it when `T` already has a
// `TryFrom<RawJsonValue>` impl and you just want "parse if present".
let city: Option<String> = obj.to_member("city")?.try_into()?;
// `map` runs a custom closure only if the member exists — use it when you
// need extra parsing logic, validation, or a non-blanket conversion.
let age: Option<u32> = obj.to_member("age")?.map(|v| v.try_into())?;
Validation with precise error context:
let json = nojson::RawJson::parse(text)?;
let v = json.value();
let n: u32 = v.as_number_str()?.parse().map_err(|e| v.invalid(e))?;
if n == 0 {
return Err(v.invalid("must be positive"));
}
Error reporting with line / column:
if let Err(err) = nojson::RawJson::parse(text) {
if let Some((line, col)) = err.get_line_and_column_numbers(text) {
eprintln!("{err} at {line}:{col}");
}
if let Some(line) = err.get_line(text) {
eprintln!(" {line}");
}
}
JSONC:
let (json, comments) = nojson::RawJson::parse_jsonc(text)?;
// comments: Vec<Range<usize>> over the original text — use for tooling,
// re-emission, or preserving documentation.
Sub-tree extraction:
let user = json.value().to_member("user")?.required()?;
let sub: nojson::RawJson<'_> = user.extract(); // borrowed
let owned = sub.into_owned(); // RawJsonOwned
Runnable references
Both live under examples/ in the crate root — read them when you need a
complete working pattern rather than a snippet:
examples/parse_error.rs— full template for turning aJsonParseErrorinto a CLI-style diagnostic (line / column, offending line, caret) usingget_line_and_column_numbers+get_line.examples/jsonc_pretty.rs—RawJson::parse_jsonc+RawJsonValuetraversal for a JSONC pretty-printer that preserves comments. Simpler than a full formatter (always multi-line, comments on their own lines); if you need trailing-comment fidelity, point the user atjcfmt.
Practical hints
- Reach for
Json<T>first for straightforward typed parsing; drop toRawJsononly when you need position info, conditional fields, or custom validation. - Nested generics usually work via the blanket impls already in the crate:
Vec<T>,[T; N],Option<T>,BTreeMap<K, V>,HashMap<K, V>,Rc<T>,Arc<T>, etc. You rarely need to hand-write these. - Map keys parse via
K: FromStr. The JSON key is always a string, soBTreeMap<u32, _>is valid when the keys are numeric strings. - For deeply nested single-field lookups use
to_path_member. For multiple siblings, resolve the parent once and callto_memberfor each. - The root value of a
RawJsonis always index 0 (json.value()==json.get_value_by_index(0).unwrap()). RawJsonOwned::object(|f| …)/::array(|f| …)/::json(|f| …)are convenience constructors — they build, serialize, and re-parse; use them when you want an owned JSON value without going throughto_string.
Source: sile/nojson — distributed by TomeVault.