Skip to main content

aprender_contracts_cli/
json_obj.rs

1//! JSON object construction that does not go through `serde_json::json!`.
2//!
3//! `serde_json::json!` expands to `Result::unwrap` internally, and this repo
4//! bans unwrap via `.clippy.toml` disallowed-methods (GH-41). The ban fired on
5//! the `pv` command surface only after that surface moved from `main.rs` into
6//! `lib.rs` — the diagnostics were real the whole time, just charged to a bin
7//! target nothing linted.
8//!
9//! The usual fix elsewhere in this workspace is a local
10//! `#[derive(serde::Serialize)]` struct (see
11//! `aprender-qa-cli/src/main_tickets_and_parity.rs::save_tool_results_json_or_exit`).
12//! `aprender-contracts-cli` depends on `serde_json` but not on `serde` itself,
13//! so it builds the `serde_json::Map` directly instead. That is the same
14//! representation `json!` produces — identical keys, identical key ordering
15//! (`Map` is the one canonical map type either way), identical nesting — with
16//! no unwrap anywhere.
17
18use serde_json::{Map, Value};
19
20/// Build a JSON object from an ordered list of `(key, value)` pairs.
21///
22/// Drop-in replacement for `serde_json::json!({ "k": v, ... })` where every
23/// key is a literal.
24pub fn obj<I>(pairs: I) -> Value
25where
26    I: IntoIterator<Item = (&'static str, Value)>,
27{
28    Value::Object(
29        pairs
30            .into_iter()
31            .map(|(k, v)| (k.to_owned(), v))
32            .collect::<Map<String, Value>>(),
33    )
34}
35
36#[cfg(test)]
37mod tests {
38    use super::obj;
39    use serde_json::Value;
40
41    #[test]
42    fn obj_builds_the_same_shape_as_the_json_macro() {
43        let built = obj([
44            ("name", Value::from("x")),
45            ("count", Value::from(3usize)),
46            ("ok", Value::from(true)),
47        ]);
48        let parsed: Value = serde_json::from_str(r#"{"name":"x","count":3,"ok":true}"#)
49            .expect("literal is valid JSON");
50        assert_eq!(built, parsed);
51    }
52
53    #[test]
54    fn obj_nests() {
55        let built = obj([("outer", obj([("inner", Value::from(1u64))]))]);
56        assert_eq!(built["outer"]["inner"], Value::from(1u64));
57    }
58
59    #[test]
60    fn obj_with_no_pairs_is_an_empty_object() {
61        let built = obj([]);
62        assert_eq!(built, Value::Object(serde_json::Map::new()));
63    }
64}