๐Ÿฆ€ Functional Rust

955: JSON Value Type

Difficulty: Beginner Category: Data Structures / Serialization Concept: Recursive algebraic data types for JSON representation Key Insight: OCaml's variant types and Rust's enums are both sum types โ€” `type json = ...` maps directly to `enum JsonValue { ... }` with identical variants
// 955: JSON Value Type
// OCaml: type json = Null | Bool of bool | Number of float | Str of string | Array of json list | Object of (string * json) list
// Rust: enum JsonValue with derived traits

// Approach 1: Direct enum translation
#[derive(Debug, Clone, PartialEq)]
pub enum JsonValue {
    Null,
    Bool(bool),
    Number(f64),
    Str(String),
    Array(Vec<JsonValue>),
    Object(Vec<(String, JsonValue)>),
}

// Approach 2: Type checks and simple display
impl JsonValue {
    pub fn is_null(&self) -> bool { matches!(self, JsonValue::Null) }
    pub fn is_bool(&self) -> bool { matches!(self, JsonValue::Bool(_)) }
    pub fn is_number(&self) -> bool { matches!(self, JsonValue::Number(_)) }
    pub fn is_string(&self) -> bool { matches!(self, JsonValue::Str(_)) }
    pub fn is_array(&self) -> bool { matches!(self, JsonValue::Array(_)) }
    pub fn is_object(&self) -> bool { matches!(self, JsonValue::Object(_)) }

    pub fn to_string_simple(&self) -> String {
        match self {
            JsonValue::Null => "null".to_string(),
            JsonValue::Bool(true) => "true".to_string(),
            JsonValue::Bool(false) => "false".to_string(),
            JsonValue::Number(n) => {
                if n.fract() == 0.0 && n.is_finite() {
                    format!("{}", *n as i64)
                } else {
                    format!("{}", n)
                }
            }
            JsonValue::Str(s) => format!("\"{}\"", s),
            JsonValue::Array(_) => "[...]".to_string(),
            JsonValue::Object(_) => "{...}".to_string(),
        }
    }
}

// Approach 3: Builder helpers
impl JsonValue {
    pub fn object(pairs: &[(&str, JsonValue)]) -> Self {
        JsonValue::Object(pairs.iter().map(|(k, v)| (k.to_string(), v.clone())).collect())
    }

    pub fn array(items: Vec<JsonValue>) -> Self {
        JsonValue::Array(items)
    }
}

fn main() {
    let j_null = JsonValue::Null;
    let j_bool = JsonValue::Bool(true);
    let j_num = JsonValue::Number(42.0);
    let j_str = JsonValue::Str("hello".to_string());
    let j_arr = JsonValue::array(vec![
        JsonValue::Number(1.0),
        JsonValue::Number(2.0),
        JsonValue::Number(3.0),
    ]);
    let j_obj = JsonValue::object(&[
        ("name", JsonValue::Str("Alice".to_string())),
        ("age", JsonValue::Number(30.0)),
    ]);

    println!("null:   {}", j_null.to_string_simple());
    println!("bool:   {}", j_bool.to_string_simple());
    println!("number: {}", j_num.to_string_simple());
    println!("string: {}", j_str.to_string_simple());
    println!("array:  {}", j_arr.to_string_simple());
    println!("object: {}", j_obj.to_string_simple());
    println!("equal:  {}", JsonValue::Bool(true) == JsonValue::Bool(true));
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_type_checks() {
        assert!(JsonValue::Null.is_null());
        assert!(JsonValue::Bool(true).is_bool());
        assert!(JsonValue::Number(1.0).is_number());
        assert!(JsonValue::Str("x".into()).is_string());
        assert!(JsonValue::Array(vec![]).is_array());
        assert!(JsonValue::Object(vec![]).is_object());
    }

    #[test]
    fn test_to_string_simple() {
        assert_eq!(JsonValue::Null.to_string_simple(), "null");
        assert_eq!(JsonValue::Bool(true).to_string_simple(), "true");
        assert_eq!(JsonValue::Bool(false).to_string_simple(), "false");
        assert_eq!(JsonValue::Number(42.0).to_string_simple(), "42");
        assert_eq!(JsonValue::Str("hello".into()).to_string_simple(), "\"hello\"");
        assert_eq!(JsonValue::Array(vec![]).to_string_simple(), "[...]");
        assert_eq!(JsonValue::Object(vec![]).to_string_simple(), "{...}");
    }

    #[test]
    fn test_equality() {
        assert_eq!(JsonValue::Null, JsonValue::Null);
        assert_eq!(JsonValue::Bool(true), JsonValue::Bool(true));
        assert_ne!(JsonValue::Bool(true), JsonValue::Bool(false));
        assert_eq!(JsonValue::Number(1.0), JsonValue::Number(1.0));
        let arr1 = JsonValue::Array(vec![JsonValue::Null, JsonValue::Bool(true)]);
        let arr2 = JsonValue::Array(vec![JsonValue::Null, JsonValue::Bool(true)]);
        assert_eq!(arr1, arr2);
    }

    #[test]
    fn test_nested_object() {
        let obj = JsonValue::object(&[
            ("name", JsonValue::Str("Alice".into())),
            ("age", JsonValue::Number(30.0)),
            ("active", JsonValue::Bool(true)),
        ]);
        assert!(obj.is_object());
        if let JsonValue::Object(pairs) = &obj {
            assert_eq!(pairs.len(), 3);
            assert_eq!(pairs[0].0, "name");
        }
    }
}
(* 955: JSON Value Type *)
(* Approach 1: Algebraic data type definition *)

type json =
  | Null
  | Bool of bool
  | Number of float
  | Str of string
  | Array of json list
  | Object of (string * json) list

(* Approach 2: Constructors and basic operations *)

let to_string_simple j =
  match j with
  | Null -> "null"
  | Bool true -> "true"
  | Bool false -> "false"
  | Number n ->
    if Float.is_integer n then string_of_int (int_of_float n)
    else Printf.sprintf "%g" n
  | Str s -> Printf.sprintf "\"%s\"" s
  | Array _ -> "[...]"
  | Object _ -> "{...}"

let is_null = function Null -> true | _ -> false
let is_bool = function Bool _ -> true | _ -> false
let is_number = function Number _ -> true | _ -> false
let is_string = function Str _ -> true | _ -> false
let is_array = function Array _ -> true | _ -> false
let is_object = function Object _ -> true | _ -> false

(* Approach 3: Pattern matching and equality *)

let rec equal a b = match a, b with
  | Null, Null -> true
  | Bool x, Bool y -> x = y
  | Number x, Number y -> x = y
  | Str x, Str y -> x = y
  | Array xs, Array ys ->
    List.length xs = List.length ys &&
    List.for_all2 equal xs ys
  | Object xs, Object ys ->
    List.length xs = List.length ys &&
    List.for_all2 (fun (k1,v1) (k2,v2) -> k1 = k2 && equal v1 v2) xs ys
  | _ -> false

let () =
  let j_null = Null in
  let j_bool = Bool true in
  let j_num = Number 42.0 in
  let j_str = Str "hello" in
  let j_arr = Array [Number 1.0; Number 2.0; Number 3.0] in
  let j_obj = Object [("name", Str "Alice"); ("age", Number 30.0)] in

  assert (is_null j_null);
  assert (is_bool j_bool);
  assert (is_number j_num);
  assert (is_string j_str);
  assert (is_array j_arr);
  assert (is_object j_obj);

  assert (to_string_simple j_null = "null");
  assert (to_string_simple j_bool = "true");
  assert (to_string_simple j_num = "42");
  assert (to_string_simple j_str = "\"hello\"");

  assert (equal Null Null);
  assert (equal (Bool true) (Bool true));
  assert (not (equal (Bool true) (Bool false)));
  assert (equal (Number 1.0) (Number 1.0));
  assert (equal (Array [Null; Bool true]) (Array [Null; Bool true]));
  assert (not (equal (Array [Null]) (Array [Bool true])));

  Printf.printf "โœ“ All tests passed\n"

๐Ÿ“Š Detailed Comparison

JSON Value Type โ€” Comparison

Core Insight

Both OCaml and Rust model JSON as a recursive algebraic data type. The mapping is nearly 1:1: OCaml variants become Rust enum variants, `list` becomes `Vec`, and `string` becomes `String`. The key difference is Rust requires explicit `PartialEq` derivation while OCaml's structural equality is built-in.

OCaml Approach

  • `type json = Null | Bool of bool | ...` โ€” recursive variant type
  • Pattern matching with `match` exhaustively handles all cases
  • Structural equality (`=`) works automatically for all types
  • `of (string * json) list` naturally models JSON objects as association lists
  • Recursive types require no special annotation (OCaml handles it)

Rust Approach

  • `enum JsonValue { Null, Bool(bool), ... }` โ€” direct enum translation
  • `Box<T>` not needed here since `Vec` provides indirection for recursion
  • `#[derive(Debug, Clone, PartialEq)]` adds traits OCaml has by default
  • `matches!` macro for concise type-checking predicates
  • Strings are `String` (owned), not `&str` (borrowed), for owned data

Comparison Table

AspectOCamlRust
Type definition`type json = Null \Bool of bool \...``enum JsonValue { Null, Bool(bool), ... }`
Structural equalityBuilt-in `=``#[derive(PartialEq)]`
List type`json list``Vec<JsonValue>`
String type`string``String`
Object representation`(string * json) list``Vec<(String, JsonValue)>`
Pattern matching`match j with \Null -> ...``match self { JsonValue::Null => ... }`
Recursive typesImplicitImplicit (via Vec/Box)
Clone`let j2 = j` (GC copies)`#[derive(Clone)]` explicit