Skip to main content

oxiproto_reflect/native/
json.rs

1//! Canonical protobuf-JSON encoding/decoding for native [`DynamicMessage`].
2//!
3//! Implements the [proto3 JSON mapping] as described in the protobuf language
4//! specification:
5//!
6//! - Field names: JSON key is the field's `json_name` (camelCase by default).
7//! - Integer types: encoded as JSON numbers; `int64`/`uint64`/`sfixed64`/
8//!   `fixed64`/`sint64` encoded as JSON strings (to preserve 64-bit precision).
9//! - Float/double: JSON numbers; `NaN`/`Infinity`/`-Infinity` encoded as the
10//!   string literals `"NaN"`, `"Infinity"`, `"-Infinity"`.
11//! - Bytes: base64-encoded JSON string (standard alphabet, with padding).
12//! - Enums: JSON string containing the enum value name; unknown numbers
13//!   encoded as the integer.
14//! - Repeated fields: JSON array.
15//! - Map fields: JSON object with stringified keys.
16//! - Nested messages: nested JSON objects.
17//! - Proto3 default-valued singular scalar fields are *omitted* from output.
18//! - `null` in input is treated as the default value for the field type.
19//! - Unknown keys in input are silently skipped.
20//!
21//! [proto3 JSON mapping]: https://protobuf.dev/programming-guides/proto3/#json
22
23use std::collections::HashMap;
24use std::sync::Arc;
25
26use super::descriptor::{Cardinality, FieldDescriptor, Kind, MessageDescriptor};
27use super::dynamic::{is_field_value_default, DynamicMessage};
28use super::value::{MapKey, Value};
29// Note: base64 and serde_json are workspace dependencies available unconditionally.
30
31// ---------------------------------------------------------------------------
32// Public error type
33// ---------------------------------------------------------------------------
34
35/// Errors produced during protobuf-JSON conversion.
36#[derive(Debug)]
37pub enum JsonError {
38    /// The input is not valid JSON.
39    InvalidJson(serde_json::Error),
40    /// The JSON structure does not match the message schema.
41    Schema(String),
42    /// An enum value name could not be resolved.
43    UnknownEnumValue(String),
44}
45
46impl std::fmt::Display for JsonError {
47    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
48        match self {
49            JsonError::InvalidJson(e) => write!(f, "invalid JSON: {e}"),
50            JsonError::Schema(s) => write!(f, "schema mismatch: {s}"),
51            JsonError::UnknownEnumValue(s) => write!(f, "unknown enum value: {s}"),
52        }
53    }
54}
55
56impl std::error::Error for JsonError {
57    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
58        match self {
59            JsonError::InvalidJson(e) => Some(e),
60            _ => None,
61        }
62    }
63}
64
65impl From<JsonError> for crate::ReflectError {
66    fn from(e: JsonError) -> Self {
67        crate::ReflectError::Field(e.to_string())
68    }
69}
70
71// ---------------------------------------------------------------------------
72// Public API on DynamicMessage
73// ---------------------------------------------------------------------------
74
75impl DynamicMessage {
76    /// Encode this message to canonical protobuf-JSON, returning a
77    /// [`serde_json::Value`] tree.
78    ///
79    /// Proto3 default-valued singular scalar fields are omitted.
80    ///
81    /// # Errors
82    ///
83    /// Returns [`JsonError`] if the message contains an unsupported feature
84    /// (e.g. a group-kind field).
85    pub fn to_json(&self) -> Result<serde_json::Value, JsonError> {
86        encode_message(self)
87    }
88
89    /// Encode this message to a canonical protobuf-JSON string.
90    ///
91    /// # Errors
92    ///
93    /// See [`DynamicMessage::to_json`].
94    pub fn to_json_string(&self) -> Result<String, JsonError> {
95        let v = self.to_json()?;
96        serde_json::to_string(&v).map_err(JsonError::InvalidJson)
97    }
98
99    /// Decode a protobuf-JSON [`serde_json::Value`] into a new
100    /// [`DynamicMessage`] of the given descriptor.
101    ///
102    /// Unknown JSON keys are silently skipped. `null` values are treated as
103    /// the type default (clearing the field).
104    ///
105    /// # Errors
106    ///
107    /// Returns [`JsonError`] if the JSON does not match the schema.
108    pub fn from_json(desc: MessageDescriptor, json: &serde_json::Value) -> Result<Self, JsonError> {
109        decode_message(desc, json)
110    }
111
112    /// Decode a protobuf-JSON string into a new [`DynamicMessage`] of the
113    /// given descriptor.
114    ///
115    /// # Errors
116    ///
117    /// Returns [`JsonError`] if the string is not valid JSON or does not match
118    /// the schema.
119    pub fn from_json_str(desc: MessageDescriptor, s: &str) -> Result<Self, JsonError> {
120        let json: serde_json::Value = serde_json::from_str(s).map_err(JsonError::InvalidJson)?;
121        decode_message(desc, &json)
122    }
123}
124
125// ---------------------------------------------------------------------------
126// Encoding
127// ---------------------------------------------------------------------------
128
129fn encode_message(msg: &DynamicMessage) -> Result<serde_json::Value, JsonError> {
130    let mut map = serde_json::Map::new();
131    let desc = msg.descriptor();
132
133    for field in desc.fields() {
134        let value = msg.get_field(&field);
135        // Omit proto3 default-valued singular fields.
136        if is_field_value_default(&field, &value) {
137            continue;
138        }
139        let json_value = encode_field_value(&value, &field)?;
140        map.insert(field.json_name().to_owned(), json_value);
141    }
142
143    Ok(serde_json::Value::Object(map))
144}
145
146fn encode_field_value(
147    value: &Value,
148    field: &FieldDescriptor,
149) -> Result<serde_json::Value, JsonError> {
150    if field.is_map() {
151        return encode_map(value, field);
152    }
153    if matches!(field.cardinality(), Cardinality::Repeated) {
154        return encode_list(value, field);
155    }
156    encode_singular(value, field)
157}
158
159fn encode_list(value: &Value, field: &FieldDescriptor) -> Result<serde_json::Value, JsonError> {
160    match value {
161        Value::List(items) => {
162            let mut arr = Vec::with_capacity(items.len());
163            for item in items {
164                arr.push(encode_singular(item, field)?);
165            }
166            Ok(serde_json::Value::Array(arr))
167        }
168        other => Err(JsonError::Schema(format!(
169            "expected list for repeated field '{}', got {:?}",
170            field.name(),
171            other
172        ))),
173    }
174}
175
176fn encode_map(value: &Value, field: &FieldDescriptor) -> Result<serde_json::Value, JsonError> {
177    match value {
178        Value::Map(entries) => {
179            let val_field = field.map_entry_value_field().ok_or_else(|| {
180                JsonError::Schema(format!(
181                    "map field '{}' missing value field descriptor",
182                    field.name()
183                ))
184            })?;
185            let mut obj = serde_json::Map::new();
186            // Sort by string key for deterministic output.
187            let mut sorted: Vec<_> = entries.iter().collect();
188            sorted.sort_by_key(|(k, _)| map_key_to_string(k));
189            for (k, v) in sorted {
190                obj.insert(map_key_to_string(k), encode_singular(v, &val_field)?);
191            }
192            Ok(serde_json::Value::Object(obj))
193        }
194        other => Err(JsonError::Schema(format!(
195            "expected map for map field '{}', got {:?}",
196            field.name(),
197            other
198        ))),
199    }
200}
201
202/// Encode a single (non-repeated, non-map) field value.
203fn encode_singular(value: &Value, field: &FieldDescriptor) -> Result<serde_json::Value, JsonError> {
204    match value {
205        Value::F64(v) => encode_f64(*v),
206        Value::F32(v) => encode_f64(f64::from(*v)),
207        Value::I32(v) => Ok(serde_json::json!(*v)),
208        Value::U32(v) => Ok(serde_json::json!(*v)),
209        // 64-bit integers: JSON string to preserve full precision.
210        Value::I64(v) => Ok(serde_json::Value::String(v.to_string())),
211        Value::U64(v) => Ok(serde_json::Value::String(v.to_string())),
212        Value::Bool(v) => Ok(serde_json::Value::Bool(*v)),
213        Value::String(s) => Ok(serde_json::Value::String(s.clone())),
214        Value::Bytes(b) => encode_bytes(b),
215        Value::EnumNumber(n) => encode_enum_number(*n, field),
216        Value::Message(m) => encode_message(m),
217        Value::List(_) | Value::Map(_) => Err(JsonError::Schema(format!(
218            "unexpected list/map in singular context for field '{}'",
219            field.name()
220        ))),
221    }
222}
223
224fn encode_f64(v: f64) -> Result<serde_json::Value, JsonError> {
225    if v.is_nan() {
226        return Ok(serde_json::Value::String("NaN".to_owned()));
227    }
228    if v.is_infinite() {
229        let s = if v > 0.0 { "Infinity" } else { "-Infinity" };
230        return Ok(serde_json::Value::String(s.to_owned()));
231    }
232    serde_json::Number::from_f64(v)
233        .map(serde_json::Value::Number)
234        .ok_or_else(|| JsonError::Schema(format!("cannot represent f64 as JSON number: {v}")))
235}
236
237fn encode_bytes(b: &[u8]) -> Result<serde_json::Value, JsonError> {
238    use base64::Engine as _;
239    Ok(serde_json::Value::String(
240        base64::engine::general_purpose::STANDARD.encode(b),
241    ))
242}
243
244fn encode_enum_number(n: i32, field: &FieldDescriptor) -> Result<serde_json::Value, JsonError> {
245    // Emit the enum value name if we can resolve it.
246    if let Some(enum_desc) = field.enum_type() {
247        if let Some(val_desc) = enum_desc.get_value(n) {
248            return Ok(serde_json::Value::String(val_desc.name().to_owned()));
249        }
250    }
251    // Unknown enum number: emit as integer (proto3 JSON rule).
252    Ok(serde_json::json!(n))
253}
254
255fn map_key_to_string(key: &MapKey) -> String {
256    match key {
257        MapKey::String(s) => s.clone(),
258        MapKey::I32(v) => v.to_string(),
259        MapKey::I64(v) => v.to_string(),
260        MapKey::U32(v) => v.to_string(),
261        MapKey::U64(v) => v.to_string(),
262        MapKey::Bool(v) => if *v { "true" } else { "false" }.to_owned(),
263    }
264}
265
266// ---------------------------------------------------------------------------
267// Decoding
268// ---------------------------------------------------------------------------
269
270fn decode_message(
271    desc: MessageDescriptor,
272    json: &serde_json::Value,
273) -> Result<DynamicMessage, JsonError> {
274    let obj = match json {
275        serde_json::Value::Object(m) => m,
276        // null at message level → empty message.
277        serde_json::Value::Null => return Ok(DynamicMessage::new(desc)),
278        other => {
279            return Err(JsonError::Schema(format!(
280                "expected JSON object for message, got {}",
281                json_type_name(other)
282            )));
283        }
284    };
285
286    let mut msg = DynamicMessage::new(desc.clone());
287
288    for (json_key, json_val) in obj {
289        // Accept both json_name (camelCase) and snake_case field names.
290        let field = desc
291            .get_field_by_json_name(json_key)
292            .or_else(|| desc.get_field_by_name(json_key));
293
294        let field = match field {
295            Some(f) => f,
296            // Unknown key — silently skip (proto3 JSON interop rule).
297            None => continue,
298        };
299
300        // null → treat as default (clear the field).
301        if json_val.is_null() {
302            continue;
303        }
304
305        let value = decode_field_value(json_val, &field)?;
306        msg.set_field(&field, value);
307    }
308
309    Ok(msg)
310}
311
312fn decode_field_value(
313    json: &serde_json::Value,
314    field: &FieldDescriptor,
315) -> Result<Value, JsonError> {
316    if field.is_map() {
317        return decode_map(json, field);
318    }
319    if matches!(field.cardinality(), Cardinality::Repeated) {
320        return decode_list(json, field);
321    }
322    decode_singular(json, field)
323}
324
325fn decode_list(json: &serde_json::Value, field: &FieldDescriptor) -> Result<Value, JsonError> {
326    let arr = match json {
327        serde_json::Value::Array(a) => a,
328        other => {
329            return Err(JsonError::Schema(format!(
330                "expected JSON array for repeated field '{}', got {}",
331                field.name(),
332                json_type_name(other)
333            )));
334        }
335    };
336    let mut items = Vec::with_capacity(arr.len());
337    for item in arr {
338        items.push(decode_singular(item, field)?);
339    }
340    Ok(Value::List(items))
341}
342
343fn decode_map(json: &serde_json::Value, field: &FieldDescriptor) -> Result<Value, JsonError> {
344    let obj = match json {
345        serde_json::Value::Object(o) => o,
346        other => {
347            return Err(JsonError::Schema(format!(
348                "expected JSON object for map field '{}', got {}",
349                field.name(),
350                json_type_name(other)
351            )));
352        }
353    };
354
355    let key_field = field.map_entry_key_field().ok_or_else(|| {
356        JsonError::Schema(format!(
357            "map field '{}' missing key field descriptor",
358            field.name()
359        ))
360    })?;
361    let val_field = field.map_entry_value_field().ok_or_else(|| {
362        JsonError::Schema(format!(
363            "map field '{}' missing value field descriptor",
364            field.name()
365        ))
366    })?;
367
368    let mut map = HashMap::new();
369    for (k_str, v_json) in obj {
370        let map_key = parse_map_key(k_str, key_field.kind())?;
371        let map_val = decode_singular(v_json, &val_field)?;
372        map.insert(map_key, map_val);
373    }
374    Ok(Value::Map(map))
375}
376
377fn parse_map_key(s: &str, kind: Kind) -> Result<MapKey, JsonError> {
378    match kind {
379        Kind::String => Ok(MapKey::String(s.to_owned())),
380        Kind::Bool => match s {
381            "true" => Ok(MapKey::Bool(true)),
382            "false" => Ok(MapKey::Bool(false)),
383            other => Err(JsonError::Schema(format!("invalid bool map key: {other}"))),
384        },
385        Kind::Int32 | Kind::Sint32 | Kind::Sfixed32 => s
386            .parse::<i32>()
387            .map(MapKey::I32)
388            .map_err(|_| JsonError::Schema(format!("invalid int32 map key: {s}"))),
389        Kind::Int64 | Kind::Sint64 | Kind::Sfixed64 => s
390            .parse::<i64>()
391            .map(MapKey::I64)
392            .map_err(|_| JsonError::Schema(format!("invalid int64 map key: {s}"))),
393        Kind::Uint32 | Kind::Fixed32 => s
394            .parse::<u32>()
395            .map(MapKey::U32)
396            .map_err(|_| JsonError::Schema(format!("invalid uint32 map key: {s}"))),
397        Kind::Uint64 | Kind::Fixed64 => s
398            .parse::<u64>()
399            .map(MapKey::U64)
400            .map_err(|_| JsonError::Schema(format!("invalid uint64 map key: {s}"))),
401        other => Err(JsonError::Schema(format!(
402            "unsupported map key kind: {other:?}"
403        ))),
404    }
405}
406
407/// Decode a single (non-repeated, non-map) JSON value using the full field
408/// descriptor for enum-name lookup and nested-message construction.
409fn decode_singular(json: &serde_json::Value, field: &FieldDescriptor) -> Result<Value, JsonError> {
410    match field.kind() {
411        Kind::Double => decode_f64(json),
412        Kind::Float => decode_f32(json),
413        Kind::Int32 | Kind::Sint32 | Kind::Sfixed32 => decode_i32(json),
414        Kind::Int64 | Kind::Sint64 | Kind::Sfixed64 => decode_i64(json),
415        Kind::Uint32 | Kind::Fixed32 => decode_u32(json),
416        Kind::Uint64 | Kind::Fixed64 => decode_u64(json),
417        Kind::Bool => decode_bool(json),
418        Kind::String => decode_string_val(json),
419        Kind::Bytes => decode_bytes_val(json),
420        Kind::Enum(_) => decode_enum(json, field),
421        Kind::Message(msg_index) => {
422            if json.is_null() {
423                let msg_desc = MessageDescriptor {
424                    pool: Arc::clone(&field.pool),
425                    index: msg_index,
426                };
427                return Ok(Value::Message(Box::new(DynamicMessage::new(msg_desc))));
428            }
429            let msg_desc = MessageDescriptor {
430                pool: Arc::clone(&field.pool),
431                index: msg_index,
432            };
433            Ok(Value::Message(Box::new(decode_message(msg_desc, json)?)))
434        }
435        Kind::Group(_) => Err(JsonError::Schema(
436            "group fields are not supported in JSON".to_owned(),
437        )),
438    }
439}
440
441fn decode_f64(json: &serde_json::Value) -> Result<Value, JsonError> {
442    match json {
443        serde_json::Value::Number(n) => {
444            Ok(Value::F64(n.as_f64().ok_or_else(|| {
445                JsonError::Schema("number out of f64 range".to_owned())
446            })?))
447        }
448        serde_json::Value::String(s) => match s.as_str() {
449            "NaN" => Ok(Value::F64(f64::NAN)),
450            "Infinity" => Ok(Value::F64(f64::INFINITY)),
451            "-Infinity" => Ok(Value::F64(f64::NEG_INFINITY)),
452            other => other
453                .parse::<f64>()
454                .map(Value::F64)
455                .map_err(|_| JsonError::Schema(format!("invalid f64 string: {other}"))),
456        },
457        other => Err(type_mismatch("f64", other)),
458    }
459}
460
461fn decode_f32(json: &serde_json::Value) -> Result<Value, JsonError> {
462    match json {
463        serde_json::Value::Number(n) => {
464            Ok(Value::F32(n.as_f64().map(|v| v as f32).ok_or_else(
465                || JsonError::Schema("number out of range for f32".to_owned()),
466            )?))
467        }
468        serde_json::Value::String(s) => match s.as_str() {
469            "NaN" => Ok(Value::F32(f32::NAN)),
470            "Infinity" => Ok(Value::F32(f32::INFINITY)),
471            "-Infinity" => Ok(Value::F32(f32::NEG_INFINITY)),
472            other => other
473                .parse::<f32>()
474                .map(Value::F32)
475                .map_err(|_| JsonError::Schema(format!("invalid f32 string: {other}"))),
476        },
477        other => Err(type_mismatch("f32", other)),
478    }
479}
480
481fn decode_i32(json: &serde_json::Value) -> Result<Value, JsonError> {
482    match json {
483        serde_json::Value::Number(n) => n
484            .as_i64()
485            .and_then(|v| i32::try_from(v).ok())
486            .map(Value::I32)
487            .ok_or_else(|| JsonError::Schema(format!("value out of i32 range: {n}"))),
488        serde_json::Value::String(s) => s
489            .parse::<i32>()
490            .map(Value::I32)
491            .map_err(|_| JsonError::Schema(format!("invalid i32 string: {s}"))),
492        other => Err(type_mismatch("i32", other)),
493    }
494}
495
496fn decode_i64(json: &serde_json::Value) -> Result<Value, JsonError> {
497    match json {
498        serde_json::Value::Number(n) => n
499            .as_i64()
500            .map(Value::I64)
501            .ok_or_else(|| JsonError::Schema(format!("value out of i64 range: {n}"))),
502        serde_json::Value::String(s) => s
503            .parse::<i64>()
504            .map(Value::I64)
505            .map_err(|_| JsonError::Schema(format!("invalid i64 string: {s}"))),
506        other => Err(type_mismatch("i64", other)),
507    }
508}
509
510fn decode_u32(json: &serde_json::Value) -> Result<Value, JsonError> {
511    match json {
512        serde_json::Value::Number(n) => n
513            .as_u64()
514            .and_then(|v| u32::try_from(v).ok())
515            .map(Value::U32)
516            .ok_or_else(|| JsonError::Schema(format!("value out of u32 range: {n}"))),
517        serde_json::Value::String(s) => s
518            .parse::<u32>()
519            .map(Value::U32)
520            .map_err(|_| JsonError::Schema(format!("invalid u32 string: {s}"))),
521        other => Err(type_mismatch("u32", other)),
522    }
523}
524
525fn decode_u64(json: &serde_json::Value) -> Result<Value, JsonError> {
526    match json {
527        serde_json::Value::Number(n) => n
528            .as_u64()
529            .map(Value::U64)
530            .ok_or_else(|| JsonError::Schema(format!("value out of u64 range: {n}"))),
531        serde_json::Value::String(s) => s
532            .parse::<u64>()
533            .map(Value::U64)
534            .map_err(|_| JsonError::Schema(format!("invalid u64 string: {s}"))),
535        other => Err(type_mismatch("u64", other)),
536    }
537}
538
539fn decode_bool(json: &serde_json::Value) -> Result<Value, JsonError> {
540    match json {
541        serde_json::Value::Bool(b) => Ok(Value::Bool(*b)),
542        serde_json::Value::String(s) => match s.as_str() {
543            "true" => Ok(Value::Bool(true)),
544            "false" => Ok(Value::Bool(false)),
545            other => Err(JsonError::Schema(format!("invalid bool string: {other}"))),
546        },
547        other => Err(type_mismatch("bool", other)),
548    }
549}
550
551fn decode_string_val(json: &serde_json::Value) -> Result<Value, JsonError> {
552    match json {
553        serde_json::Value::String(s) => Ok(Value::String(s.clone())),
554        other => Err(type_mismatch("string", other)),
555    }
556}
557
558fn decode_bytes_val(json: &serde_json::Value) -> Result<Value, JsonError> {
559    match json {
560        serde_json::Value::String(s) => {
561            use base64::Engine as _;
562            // Accept standard alphabet with or without padding, and URL-safe.
563            let bytes = base64::engine::general_purpose::STANDARD
564                .decode(s)
565                .or_else(|_| {
566                    base64::engine::general_purpose::STANDARD_NO_PAD.decode(s.trim_end_matches('='))
567                })
568                .or_else(|_| base64::engine::general_purpose::URL_SAFE_NO_PAD.decode(s))
569                .map_err(|e| JsonError::Schema(format!("invalid base64 for bytes field: {e}")))?;
570            Ok(Value::Bytes(bytes))
571        }
572        other => Err(type_mismatch("bytes (base64 string)", other)),
573    }
574}
575
576fn decode_enum(json: &serde_json::Value, field: &FieldDescriptor) -> Result<Value, JsonError> {
577    let enum_desc = field.enum_type().ok_or_else(|| {
578        JsonError::Schema(format!(
579            "field '{}' has enum kind but no enum descriptor",
580            field.name()
581        ))
582    })?;
583
584    match json {
585        serde_json::Value::Number(n) => {
586            let num = n
587                .as_i64()
588                .and_then(|v| i32::try_from(v).ok())
589                .ok_or_else(|| JsonError::Schema(format!("enum number out of i32 range: {n}")))?;
590            Ok(Value::EnumNumber(num))
591        }
592        serde_json::Value::String(s) => enum_desc
593            .get_value_by_name(s)
594            .map(|v| Value::EnumNumber(v.number()))
595            .ok_or_else(|| JsonError::UnknownEnumValue(s.clone())),
596        other => Err(type_mismatch("enum (number or name string)", other)),
597    }
598}
599
600// ---------------------------------------------------------------------------
601// Utilities
602// ---------------------------------------------------------------------------
603
604fn json_type_name(v: &serde_json::Value) -> &'static str {
605    match v {
606        serde_json::Value::Null => "null",
607        serde_json::Value::Bool(_) => "bool",
608        serde_json::Value::Number(_) => "number",
609        serde_json::Value::String(_) => "string",
610        serde_json::Value::Array(_) => "array",
611        serde_json::Value::Object(_) => "object",
612    }
613}
614
615fn type_mismatch(expected: &str, got: &serde_json::Value) -> JsonError {
616    JsonError::Schema(format!("expected {expected}, got {}", json_type_name(got)))
617}