oxiproto-json — Canonical Protobuf-JSON mapping for OxiProto
oxiproto-json implements the canonical Protobuf-JSON mapping for runtime-reflected messages. It converts between a prost_reflect::DynamicMessage and a serde_json::Value in both directions, honouring the JSON conventions mandated by the Protobuf spec. It is the JSON layer of the OxiProto stack and is re-exported from the oxiproto facade behind the json feature.
The mapping is driven entirely by a message's runtime descriptor, so no generated Rust types are required — point it at any DynamicMessage (built via oxiproto-reflect) and a matching MessageDescriptor. The crate is 100% Pure Rust with #![forbid(unsafe_code)].
Mapping rules
| Protobuf construct | JSON encoding |
|---|---|
| Field names | camelCase by default (preserve_proto_field_names(true) keeps the original snake_case) |
int64 / uint64 / fixed64 / sfixed64 |
JSON string (preserves 64-bit precision) |
bytes |
base64 (RFC 4648 §4, standard alphabet, with padding) |
google.protobuf.Timestamp |
RFC 3339 string, e.g. "2023-11-14T22:13:20Z" |
google.protobuf.Duration |
decimal-seconds string, e.g. "1.5s" |
enum |
name string (set emit_enum_as_number(true) to emit the integer) |
repeated |
JSON array |
map<K, V> |
JSON object |
| Default scalar values | omitted unless always_print_fields(true) |
Installation
[]
= "0.1.0"
Or, via the facade:
[]
= { = "0.1.0", = ["json", "reflect"] }
Quick Start
use ;
use ;
#
Customising the codec
use JsonCodec;
let codec = new
.preserve_proto_field_names // keep snake_case keys
.always_print_fields // include proto3 defaults
.emit_enum_as_number; // enums as integers, not names
API Overview
Functions
| Function | Signature | Description |
|---|---|---|
to_json |
to_json(msg: &DynamicMessage, codec: &JsonCodec) -> serde_json::Value |
Serialize a dynamic message to canonical Protobuf-JSON |
from_json |
from_json(value: &serde_json::Value, descriptor: &MessageDescriptor, codec: &JsonCodec) -> Result<DynamicMessage, JsonError> |
Deserialize JSON into a dynamic message validated against descriptor |
JsonCodec — serialization configuration
Builder-style configuration following the canonical spec. Construct with JsonCodec::default() / JsonCodec::new(), then chain the setters (each consumes and returns Self).
| Method | Default | Description |
|---|---|---|
new() / default() |
— | camelCase keys, defaults omitted, enum names as strings |
preserve_proto_field_names(bool) |
false |
When true, emit original snake_case proto field names instead of camelCase JSON names |
always_print_fields(bool) |
false |
When true, include every field even when it holds its proto3 default value (0, "", false, empty list/map) |
emit_enum_as_number(bool) |
false |
When true, serialize enum values as integers rather than their string names |
JsonError variants
Returned by from_json. Implements std::error::Error and Display, and converts to/from oxiproto_core::OxiProtoError.
| Variant | Description |
|---|---|
WrongType { field, expected, got } |
The JSON value for a field had an incompatible type |
UnknownField(String) |
A JSON object key did not match any field in the descriptor |
MalformedValue(String) |
A scalar could not be parsed or decoded (e.g. invalid base64) |
Deferred items
The following are tracked for a future milestone and are not yet fully spec-compliant:
google.protobuf.Any— type-URL resolution requires a liveDescriptorPoolwith the target message registered; currently serialized as an empty object{}.- Non-finite floats — the spec requires
"Infinity","-Infinity", and"NaN"strings; currently emitted asnull. google.protobuf.Struct,Value,ListValue— currently treated as regular messages rather than receiving their special JSON forms.google.protobuf.FieldMask— currently treated as a regular message.
Cross-references
| Crate | Role |
|---|---|
oxiproto-core |
Native wire format, traits, and the shared OxiProtoError |
oxiproto-reflect |
Builds the DescriptorPool and DynamicMessage values this crate maps to/from JSON |
oxiproto-wkt |
Well-Known Type helpers (Timestamp, Duration, etc.) |
oxiproto |
Facade crate; re-exports this crate as oxiproto::json behind the json feature |
License
Apache-2.0 — COOLJAPAN OU (Team Kitasan)