ploidy-pointer 0.11.0

JSON Pointers for strongly-typed data structures
Documentation

ploidy-pointer

This crate provides a way to traverse typed Rust data structures using JSON Pointers (RFC 6901). At its heart is the JsonPointee trait, which can be implemented on types to make them traversable.

ploidy-pointer is part of the Ploidy OpenAPI code generator, but can be used standalone.

Features

  • Parse and resolve JSON Pointer strings.
  • Built-in JsonPointee and JsonPointerTarget implementations for primitives, collections, and common external types.
  • Derive JsonPointee and JsonPointerTarget implementations for your own types.

Cargo features

  • derive (default): Enables the #[derive(JsonPointee)] and #[derive(JsonPointerTarget)] macros.
  • did-you-mean: Adds suggestions for typos to error messages.
  • serde_json: Implements Json{Pointee, PointerTarget} for serde_json::Value.
  • chrono: Implements Json{Pointee, PointerTarget} for chrono::DateTime<Utc>.
  • url: Implements Json{Pointee, PointerTarget} for url::Url.
  • indexmap: Implements Json{Pointee, PointerTarget} for indexmap::IndexMap.
  • full: Enables all features.

JSON Pointer Syntax

JSON Pointers are strings that identify a specific value within a JSON structure:

  • "" (empty string) - References the root value.
  • "/foo" - References the foo field.
  • "/foo/0" - References the first element of the foo array.
  • "/foo/bar" - References the bar field of the foo object.

Two special characters need to be escaped: ~ is written as ~0, and / is written as ~1.

Note that "/" (a single slash) does not reference the root; it references a field named "" (the empty string). If you see an "unknown key" error for a field that you know exists, double-check that an extra slash hasn't snuck in to the pointer string.

Usage

use ploidy_pointer::JsonPointeeExt;
use std::collections::HashMap;

let mut data = HashMap::new();
data.insert("foo".to_owned(), vec![1, 2, 3]);

assert_eq!(data.pointer::<i32>("/foo/1").unwrap(), 2);

Deriving JsonPointee for your own types

The #[derive(JsonPointee)] macro generates implementations of JsonPointee for structs and enums, with Serde-like attributes for customization. #[derive(JsonPointerTarget)] generates an implementation of JsonPointerTarget that extracts a reference to the concrete type from a type-erased JsonPointee.

For more details, please see the ploidy-pointer-derive docs.

use ploidy_pointer::{JsonPointee, JsonPointer, JsonPointerTarget, JsonPointeeExt};

#[derive(JsonPointee, JsonPointerTarget)]
struct User {
    name: String,
    age: u32,
}

let user = User {
    name: "Alice".to_owned(),
    age: 30,
};

// Use `pointer()` to resolve and extract in one step...
let name: &str = user.pointer("/name").unwrap();
assert_eq!(name, "Alice");

// ...Or parse and `follow()` the pointer separately.
let pointer = JsonPointer::parse("/age").unwrap();
let age: u32 = pointer.follow(&user).unwrap();
assert_eq!(age, 30);

Errors

Type errors and missing key errors omit details by default, but you can enable the did-you-mean Cargo feature to add more context to error messages. Ploidy does this to provide more helpful errors when parsing OpenAPI documents:

let pointer = JsonPointer::parse("/naem").unwrap();
match user.resolve(pointer) {
    Ok(_) => unreachable!(),
    Err(err) => {
        // Error: unknown key "naem" for value of struct `User`;
        // did you mean "name"?
        println!("{}", err);
    }
}

Similar crates

There are many great options for working with JSON Pointers in Rust: jsonptr, json-pointer and its forks, and serde_json::Value::pointer.

For native Rust data structures, bevy_reflect and facet offer much more powerful runtime reflection capabilities.

ploidy-pointer fills a niche somewhere in between these two, providing JSON Pointers for native Rust data structures. This is especially useful for code generators like Ploidy, and strongly-typed API clients that want to navigate structured responses.

In short:

  • If you're working with structured data, and want to add type-safe JSON Pointer traversal, ploidy-pointer could be a good fit.
  • If you're working with dynamic JSON documents, and want to read and write values, consider jsonptr or json-pointer.
  • If you're working with simpler JSON values, and don't need more advanced features, the pointer() method on serde_json::Value might be enough.
  • If you'd like full runtime reflection for your structured data, give bevy_reflect or facet a try.