capnp-json 0.3.0

Cap'n Proto JSON codec for capnp-rust
Documentation

capnp-json

A Cap'n Proto JSON codec for capnp-rust, implementing the codec defined in json.capnp.

It encodes a Cap'n Proto message to JSON, and decodes JSON into a Cap'n Proto message, using the schema's runtime type information. The wire format is compatible with the C++ JSON codec that ships with Cap'n Proto.

Compatibility

Compatibile with capnp: 0.27.

capnp-json is only compatible with a particular minor release of capnp due to breaking changes and schema updates upstream.

Usage

Add the dependency:

[dependencies]
capnp = "0.27"
capnp-json = "0.3.0"

Encoding a message reader to a JSON string, and decoding JSON back into a message builder:

use capnp::message;
use capnp_json::{from_json, to_json};

# mod my_schema_capnp { capnp::generated_code!(pub mod my_schema_capnp); }
# fn run() -> capnp::Result<()> {
let mut builder = message::Builder::new_default();
let root: my_schema_capnp::my_struct::Builder<'_> = builder.init_root();
// ... populate `root` ...

let json: String = to_json(root.reborrow_as_reader())?;

let mut decoded = message::Builder::new_default();
let decoded_root: my_schema_capnp::my_struct::Builder<'_> = decoded.init_root();
from_json(&json, decoded_root)?;
# Ok(()) }

If your schema uses any of the JSON annotations ($Json.name, $Json.flatten, $Json.discriminator, $Json.base64, $Json.hex), import them by adding the following to your build.rs, so that the generated code links against the annotations defined in this crate:

fn main() {
    capnpc::CompilerCommand::new()
        .crate_provides("capnp_json", [0x8ef99297a43a5e34])
        .file("my_schema.capnp")
        .run()
        .expect("compiling schema");
}

And in your schema:

using Json = import "/capnp/compat/json.capnp";

struct MyStruct {
    myField @0 :Text $Json.name("my_field");
}

Supported features

  • All primitive Cap'n Proto types, including Int64 / UInt64 encoded as JSON strings (matching the C++ codec). Integer fields and Data bytes are range-checked on decode, so an out-of-range or fractional number is an error rather than being silently clamped; floats and enum ordinals are not checked, matching C++.
  • Float32 / Float64 NaN, Infinity, and -Infinity encoded as JSON strings.
  • Structs, lists, lists of lists, and lists of structs.
  • Enums, encoded by name (or by ordinal if the enumerant is missing).
  • Named and unnamed unions.
  • Annotations:
    • $Json.name — rename a field, enumerant, method, group, or union member in the JSON representation.
    • $Json.flatten — flatten a struct, group, or union into its parent. Flattening must terminate: a schema that flattens a field of its own type, directly or through a chain, cannot be represented as JSON. Call capnp_json::validate_schema::<MyStruct::Owned>() once (from a test, or at startup) to get the same "cyclic JSON flattening detected" verdict the C++ codec gives. Encoding and decoding skip the check, since a schema is compile-time data; a cyclic schema is still rejected when decoded, just via the recursion limit.
    • $Json.discriminator — encode a union's variant as a sibling discriminator field.
    • $Json.base64 / $Json.hex — encode Data fields as Base64 or hex strings instead of arrays of bytes.
  • Custom per-field and per-type encodings, via the FieldCodec trait and Codec::with_field_override / with_type_override. This is the equivalent of the C++ codec's Handler API, and is what makes AnyPointer and interface fields encodable.
  • Named codecs selected from the schema with this crate's own $Rust.codec("name") annotation (see rust-json.capnp), registered with Codec::with_named_codec.

Not yet supported

  • The Value / Call / raw extensions from json.capnp.
  • AnyPointer and interface fields, unless a FieldCodec is registered for them.
  • Pretty-printed output.

Known divergences from the C++ codec

These matter mainly when decoding input you do not control; see the crate documentation for the full list.

  • \uXXXX surrogate pairs are combined into the character they denote, and unpaired surrogates are rejected. C++ decodes each escape separately and produces WTF-8, which is not valid UTF-8. Round-tripping is unaffected: the C++ encoder writes non-BMP characters as literal UTF-8, never as escapes.
  • Only Int64 / UInt64 accept the string form of an integer when decoding; C++ accepts it for every integer width.

Note that a JSON null for a pointer-typed field is accepted as "field absent", matching the C++ v2 branch. That is not in any v1 C++ release, so released versions reject it — this crate is the more permissive of the two.

License

MIT