Skip to content

Commit 21c024f

Browse files
authored
doc: serde_json::Value bridging example/doc (#317)
1 parent fa9a0a1 commit 21c024f

File tree

4 files changed

+354
-1
lines changed

4 files changed

+354
-1
lines changed

README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,12 @@ enum A {
9999
}
100100
```
101101

102+
## Advanced examples
103+
104+
Some of the less trivial examples are present in [examples](./borsh/examples) folder:
105+
106+
- [implementing `BorshSerialize`/`BorshDeserialize` for third-party `serde_json::Value`](./borsh/examples/serde_json_value.rs)
107+
102108
## Testing
103109

104110
Integration tests should generally be preferred to unit ones. Root module of integration tests of `borsh` crate is [linked](./borsh/tests/tests.rs) here.

borsh-derive/src/lib.rs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -282,11 +282,15 @@ struct B<K, V> {
282282
}
283283
```
284284
285+
###### usage (comprehensive example)
286+
287+
[borsh/examples/serde_json_value.rs](https://github.com/near/borsh-rs/blob/master/borsh/examples/serde_json_value.rs) is
288+
a more complex example of how the attribute may be used.
289+
285290
###### interaction with `#[borsh(skip)]`
286291
287292
`#[borsh(serialize_with = ...)]` is not allowed to be used simultaneously with `#[borsh(skip)]`.
288293
289-
290294
*/
291295
#[proc_macro_derive(BorshSerialize, attributes(borsh))]
292296
pub fn borsh_serialize(input: TokenStream) -> TokenStream {
@@ -622,6 +626,11 @@ struct B<K: Hash + Eq, V> {
622626
}
623627
```
624628
629+
###### usage (comprehensive example)
630+
631+
[borsh/examples/serde_json_value.rs](https://github.com/near/borsh-rs/blob/master/borsh/examples/serde_json_value.rs) is
632+
a more complex example of how the attribute may be used.
633+
625634
###### interaction with `#[borsh(skip)]`
626635
627636
`#[borsh(deserialize_with = ...)]` is not allowed to be used simultaneously with `#[borsh(skip)]`.

borsh/Cargo.toml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@ exclude = ["*.snap"]
1818
name = "borsh"
1919
path = "src/lib.rs"
2020

21+
[[example]]
22+
name = "serde_json_value"
23+
required-features = ["std", "derive"]
24+
2125
[[bin]]
2226
name = "generate_schema_schema"
2327
path = "src/generate_schema_schema.rs"
@@ -39,6 +43,7 @@ bson = { version = "2", optional = true }
3943

4044
[dev-dependencies]
4145
insta = "1.29.0"
46+
serde_json = { version = "1" }
4247

4348
[package.metadata.docs.rs]
4449
features = ["derive", "unstable__schema", "rc"]

borsh/examples/serde_json_value.rs

Lines changed: 333 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,333 @@
1+
use std::collections::HashMap;
2+
3+
use borsh::{BorshDeserialize, BorshSerialize};
4+
5+
mod serde_json_value {
6+
pub use de::deserialize_value;
7+
pub use ser::serialize_value;
8+
mod ser {
9+
use borsh::{
10+
io::{ErrorKind, Result, Write},
11+
BorshSerialize,
12+
};
13+
use core::convert::TryFrom;
14+
15+
/// this is mutually recursive with `serialize_array` and `serialize_map`
16+
pub fn serialize_value<W: Write>(value: &serde_json::Value, writer: &mut W) -> Result<()> {
17+
match value {
18+
serde_json::Value::Null => 0_u8.serialize(writer),
19+
serde_json::Value::Bool(b) => {
20+
1_u8.serialize(writer)?;
21+
b.serialize(writer)
22+
}
23+
serde_json::Value::Number(n) => {
24+
2_u8.serialize(writer)?;
25+
serialize_number(n, writer)
26+
}
27+
serde_json::Value::String(s) => {
28+
3_u8.serialize(writer)?;
29+
s.serialize(writer)
30+
}
31+
serde_json::Value::Array(a) => {
32+
4_u8.serialize(writer)?;
33+
serialize_array(a, writer)
34+
}
35+
serde_json::Value::Object(o) => {
36+
5_u8.serialize(writer)?;
37+
serialize_map(o, writer)
38+
}
39+
}
40+
}
41+
42+
fn serialize_number<W: Write>(number: &serde_json::Number, writer: &mut W) -> Result<()> {
43+
// A JSON number can either be a non-negative integer (represented in
44+
// serde_json by a u64), a negative integer (by an i64), or a non-integer
45+
// (by an f64).
46+
// We identify these cases with the following single-byte discriminants:
47+
// 0 - u64
48+
// 1 - i64
49+
// 2 - f64
50+
if let Some(u) = number.as_u64() {
51+
0_u8.serialize(writer)?;
52+
return u.serialize(writer);
53+
}
54+
55+
if let Some(i) = number.as_i64() {
56+
1_u8.serialize(writer)?;
57+
return i.serialize(writer);
58+
}
59+
60+
if let Some(f) = number.as_f64() {
61+
2_u8.serialize(writer)?;
62+
return f.serialize(writer);
63+
}
64+
65+
// technically, it should not be unreachable, but an error instead,
66+
// as assumption about unreachable depends on private implementation detail
67+
// but it's fine to leave it be unreachable! for an example
68+
unreachable!("number is neither a u64, i64, nor f64");
69+
}
70+
71+
/// this is mutually recursive with `serialize_value`
72+
fn serialize_array<W: Write>(array: &Vec<serde_json::Value>, writer: &mut W) -> Result<()> {
73+
// The implementation here is very similar to that of Vec<V>.
74+
writer.write_all(
75+
&(u32::try_from(array.len()).map_err(|_| ErrorKind::InvalidData)?).to_le_bytes(),
76+
)?;
77+
for item in array {
78+
serialize_value(&item, writer)?;
79+
}
80+
Ok(())
81+
}
82+
83+
/// this is mutually recursive with `serialize_value`
84+
fn serialize_map<W: Write>(
85+
map: &serde_json::Map<String, serde_json::Value>,
86+
writer: &mut W,
87+
) -> Result<()> {
88+
// The implementation here is very similar to that of BTreeMap<K, V>.
89+
u32::try_from(map.len())
90+
.map_err(|_| ErrorKind::InvalidData)?
91+
.serialize(writer)?;
92+
93+
for (key, value) in map {
94+
key.serialize(writer)?;
95+
serialize_value(&value, writer)?;
96+
}
97+
98+
Ok(())
99+
}
100+
}
101+
mod de {
102+
use borsh::{
103+
io::{Error, ErrorKind, Read, Result},
104+
BorshDeserialize,
105+
};
106+
107+
/// this is copy-paste of https://github.com/near/borsh-rs/blob/master/borsh/src/de/hint.rs#L2-L5
108+
fn hint_cautious<T>(hint: u32) -> usize {
109+
let el_size = core::mem::size_of::<T>() as u32;
110+
core::cmp::max(core::cmp::min(hint, 4096 / el_size), 1) as usize
111+
}
112+
113+
/// this is mutually recursive with `deserialize_array`, `deserialize_map`
114+
pub fn deserialize_value<R: Read>(reader: &mut R) -> Result<serde_json::Value> {
115+
let flag: u8 = BorshDeserialize::deserialize_reader(reader)?;
116+
match flag {
117+
0 => Ok(serde_json::Value::Null),
118+
1 => {
119+
let b: bool = BorshDeserialize::deserialize_reader(reader)?;
120+
Ok(serde_json::Value::Bool(b))
121+
}
122+
2 => {
123+
let n: serde_json::Number = deserialize_number(reader)?;
124+
Ok(serde_json::Value::Number(n))
125+
}
126+
3 => {
127+
let s: String = BorshDeserialize::deserialize_reader(reader)?;
128+
Ok(serde_json::Value::String(s))
129+
}
130+
4 => {
131+
let a: Vec<serde_json::Value> = deserialize_array(reader)?;
132+
Ok(serde_json::Value::Array(a))
133+
}
134+
5 => {
135+
let o: serde_json::Map<_, _> = deserialize_map(reader)?;
136+
Ok(serde_json::Value::Object(o))
137+
}
138+
_ => {
139+
let msg = format!(
140+
"Invalid JSON value representation: {}. The first byte must be 0-5",
141+
flag
142+
);
143+
144+
Err(Error::new(ErrorKind::InvalidData, msg))
145+
}
146+
}
147+
}
148+
149+
fn deserialize_number<R: Read>(reader: &mut R) -> Result<serde_json::Number> {
150+
let flag: u8 = BorshDeserialize::deserialize_reader(reader)?;
151+
match flag {
152+
0 => {
153+
let u: u64 = BorshDeserialize::deserialize_reader(reader)?;
154+
Ok(u.into())
155+
}
156+
1 => {
157+
let i: i64 = BorshDeserialize::deserialize_reader(reader)?;
158+
Ok(i.into())
159+
}
160+
2 => {
161+
let f: f64 = BorshDeserialize::deserialize_reader(reader)?;
162+
// This returns None if the number is a NaN or +/-Infinity,
163+
// which are not valid JSON numbers.
164+
serde_json::Number::from_f64(f).ok_or_else(|| {
165+
let msg = format!("Invalid JSON number: {}", f);
166+
167+
Error::new(ErrorKind::InvalidData, msg)
168+
})
169+
}
170+
_ => {
171+
let msg = format!(
172+
"Invalid JSON number representation: {}. The first byte must be 0-2",
173+
flag
174+
);
175+
176+
Err(Error::new(ErrorKind::InvalidData, msg))
177+
}
178+
}
179+
}
180+
181+
/// this is mutually recursive with `deserialize_value`
182+
fn deserialize_array<R: Read>(reader: &mut R) -> Result<Vec<serde_json::Value>> {
183+
// The implementation here is very similar to that of Vec<V>.
184+
let len = u32::deserialize_reader(reader)?;
185+
let mut result = Vec::with_capacity(hint_cautious::<(String, serde_json::Value)>(len));
186+
for _ in 0..len {
187+
let value = deserialize_value(reader)?;
188+
result.push(value);
189+
}
190+
Ok(result)
191+
}
192+
193+
/// this is mutually recursive with `deserialize_value`
194+
fn deserialize_map<R: Read>(
195+
reader: &mut R,
196+
) -> Result<serde_json::Map<String, serde_json::Value>> {
197+
// The implementation here is very similar to that of BTreeMap<K, V>.
198+
199+
let vec: Vec<(String, serde_json::Value)> = {
200+
// The implementation here is very similar to that of Vec<(K, V)>.
201+
let len = u32::deserialize_reader(reader)?;
202+
let mut result =
203+
Vec::with_capacity(hint_cautious::<(String, serde_json::Value)>(len));
204+
for _ in 0..len {
205+
let pair = {
206+
let key = String::deserialize_reader(reader)?;
207+
let value = deserialize_value(reader)?;
208+
(key, value)
209+
};
210+
result.push(pair);
211+
}
212+
result
213+
};
214+
215+
Ok(vec.into_iter().collect())
216+
}
217+
}
218+
}
219+
220+
mod borsh_wrapper {
221+
use borsh::{BorshDeserialize, BorshSerialize};
222+
223+
#[derive(Debug, PartialEq, Eq, BorshSerialize, BorshDeserialize)]
224+
pub struct SerdeJsonBorshWrapper(
225+
#[borsh(
226+
serialize_with = "super::serde_json_value::serialize_value",
227+
deserialize_with = "super::serde_json_value::deserialize_value"
228+
)]
229+
pub serde_json::Value,
230+
);
231+
232+
impl From<serde_json::Value> for SerdeJsonBorshWrapper {
233+
fn from(value: serde_json::Value) -> Self {
234+
Self(value)
235+
}
236+
}
237+
238+
impl From<SerdeJsonBorshWrapper> for serde_json::Value {
239+
fn from(value: SerdeJsonBorshWrapper) -> Self {
240+
value.0
241+
}
242+
}
243+
}
244+
245+
use borsh_wrapper::SerdeJsonBorshWrapper;
246+
247+
#[derive(Debug, PartialEq, Eq, BorshSerialize, BorshDeserialize)]
248+
struct SerdeJsonAsField {
249+
pub examples: HashMap<String, SerdeJsonBorshWrapper>,
250+
}
251+
252+
fn main() {
253+
// original code is from https://github.com/near/borsh-rs/pull/312
254+
let original = serde_json::json!({
255+
"null": null,
256+
"true": true,
257+
"false": false,
258+
"zero": 0,
259+
"positive_integer": 12345,
260+
"negative_integer": -88888,
261+
"positive_float": 123.45,
262+
"negative_float": -888.88,
263+
"positive_max": 1.7976931348623157e+308,
264+
"negative_max": -1.7976931348623157e+308,
265+
"string": "Larry",
266+
"array_of_nulls": [null, null, null],
267+
"array_of_numbers": [0, -1, 1, 1.1, -1.1, 34798324],
268+
"array_of_strings": ["Larry", "Jake", "Pumpkin"],
269+
"array_of_arrays": [
270+
[1, 2, 3],
271+
[4, 5, 6],
272+
[7, 8, 9]
273+
],
274+
"array_of_objects": [
275+
{
276+
"name": "Larry",
277+
"age": 30
278+
},
279+
{
280+
"name": "Jake",
281+
"age": 7
282+
},
283+
{
284+
"name": "Pumpkin",
285+
"age": 8
286+
}
287+
],
288+
"object": {
289+
"name": "Larry",
290+
"age": 30,
291+
"pets": [
292+
{
293+
"name": "Jake",
294+
"age": 7
295+
},
296+
{
297+
"name": "Pumpkin",
298+
"age": 8
299+
}
300+
]
301+
}
302+
});
303+
304+
let mut examples = HashMap::new();
305+
examples.insert("Larry Jake Pumpkin".into(), original.clone().into());
306+
307+
let complex_struct = SerdeJsonAsField { examples };
308+
let serialized = borsh::to_vec(&complex_struct).unwrap();
309+
310+
let mut deserialized: SerdeJsonAsField = borsh::from_slice(&serialized).unwrap();
311+
312+
assert_eq!(complex_struct, deserialized);
313+
314+
let deserialized_value: serde_json::Value = deserialized
315+
.examples
316+
.remove("Larry Jake Pumpkin")
317+
.expect("key present")
318+
.into();
319+
320+
assert_eq!(original, deserialized_value);
321+
322+
let number = deserialized_value
323+
.get("array_of_numbers")
324+
.expect("has key")
325+
.as_array()
326+
.expect("is array")
327+
.get(5)
328+
.expect("has index")
329+
.as_i64()
330+
.expect("is i64");
331+
332+
assert_eq!(number, 34798324);
333+
}

0 commit comments

Comments
 (0)