fleetforge_common/
prost_json.rs1use std::collections::BTreeMap;
2
3use anyhow::{anyhow, Result};
4use prost_types::{value::Kind, ListValue, Struct, Value};
5use serde_json::{Map, Number};
6
7pub fn prost_struct_to_json(structure: &Struct) -> serde_json::Value {
9 let mut map = Map::new();
10 for (key, value) in &structure.fields {
11 map.insert(key.clone(), prost_value_to_json(value));
12 }
13 serde_json::Value::Object(map)
14}
15
16pub fn prost_value_to_json(value: &Value) -> serde_json::Value {
18 match value.kind.as_ref() {
19 Some(Kind::NullValue(_)) | None => serde_json::Value::Null,
20 Some(Kind::NumberValue(v)) => Number::from_f64(*v)
21 .map(serde_json::Value::Number)
22 .unwrap_or(serde_json::Value::Null),
23 Some(Kind::StringValue(v)) => serde_json::Value::String(v.clone()),
24 Some(Kind::BoolValue(v)) => serde_json::Value::Bool(*v),
25 Some(Kind::StructValue(struct_value)) => prost_struct_to_json(struct_value),
26 Some(Kind::ListValue(list)) => serde_json::Value::Array(
27 list.values
28 .iter()
29 .map(prost_value_to_json)
30 .collect::<Vec<_>>(),
31 ),
32 }
33}
34
35pub fn json_to_prost_struct(value: &serde_json::Value) -> Result<Struct> {
37 let obj = value
38 .as_object()
39 .ok_or_else(|| anyhow!("inputs must be a JSON object"))?;
40
41 let mut fields = BTreeMap::new();
42 for (key, val) in obj {
43 fields.insert(key.clone(), json_to_prost_value(val)?);
44 }
45
46 Ok(Struct { fields })
47}
48
49pub fn json_to_prost_value(value: &serde_json::Value) -> Result<Value> {
51 let kind = match value {
52 serde_json::Value::Null => Kind::NullValue(0),
53 serde_json::Value::Bool(v) => Kind::BoolValue(*v),
54 serde_json::Value::Number(num) => Kind::NumberValue(
55 num.as_f64()
56 .ok_or_else(|| anyhow!("numeric values must fit into f64 for protobuf Value"))?,
57 ),
58 serde_json::Value::String(v) => Kind::StringValue(v.clone()),
59 serde_json::Value::Array(values) => {
60 let converted = values
61 .iter()
62 .map(json_to_prost_value)
63 .collect::<Result<Vec<_>>>()?;
64 Kind::ListValue(ListValue { values: converted })
65 }
66 serde_json::Value::Object(map) => {
67 let mut fields = BTreeMap::new();
68 for (key, val) in map {
69 fields.insert(key.clone(), json_to_prost_value(val)?);
70 }
71 Kind::StructValue(Struct { fields })
72 }
73 };
74
75 Ok(Value { kind: Some(kind) })
76}
77
78#[cfg(test)]
79mod tests {
80 use super::*;
81
82 #[test]
83 fn round_trips_struct() {
84 let json = serde_json::json!({
85 "foo": true,
86 "bar": [1, "two"]
87 });
88 let struct_value = json_to_prost_struct(&json).expect("convert to struct");
89 let json_round = prost_struct_to_json(&struct_value);
90 assert_eq!(json, json_round);
91 }
92
93 #[test]
94 fn round_trips_nested_and_edge_numbers() {
95 let json = serde_json::json!({
96 "empty": {},
97 "list": [
98 {"value": 1},
99 {"value": 2.5},
100 {"value": -12345678901234567890i128.to_string()},
101 ["nested", {"deep": [null, true, 3.141592653589793]}]
102 ],
103 "scalars": {
104 "int": 2147483647,
105 "float": 1.0 / 3.0,
106 "large_float": 1.0e308,
107 "zero": 0,
108 "string": "hello"
109 }
110 });
111
112 let struct_value = json_to_prost_struct(&json).expect("convert to struct");
113 let json_round = prost_struct_to_json(&struct_value);
114
115 assert_eq!(json_round.get("empty").unwrap(), &serde_json::json!({}));
118 assert_eq!(json_round.get("list").unwrap().as_array().unwrap().len(), 4);
119 assert_eq!(json_round["list"][3][0], serde_json::json!("nested"));
120 assert!(
121 (json_round["scalars"]["float"].as_f64().unwrap() - (1.0 / 3.0)).abs() < f64::EPSILON
122 );
123 assert_eq!(
124 json_round["scalars"]["large_float"].as_f64().unwrap(),
125 1.0e308
126 );
127 assert_eq!(json_round["scalars"]["string"], serde_json::json!("hello"));
128 }
129}