pintuch
Posted on January 3, 2021
Basic struct serialization and deserialization
Serialization happens via serde_json::to_string.
serde_json::to_string_pretty
can be used for enhancing the readibility if you're printing the data.
pub fn to_string<T: ?Sized>(value: &T) -> Result<String>
where
T: Serialize
Deserialization happens via serde_json::from_str
pub fn from_str<'a, T>(s: &'a str) -> Result<T>
where
T: Deserialize<'a>
Since, the return type for both is a Result<T>
, the examples below end up unwrap()
ing the values.
Unit structs
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Debug)]
struct U;
fn main() {
let u = U;
// serialization
let s = serde_json::to_string(&u).unwrap();
// `s` represented as `null`
println!("{}", s);
// deserialization
let d: U = serde_json::from_str("null").unwrap();
// `d` is U
println!("{:?}", d);
}
// Output:
// null
// U
Tuple structs
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Debug)]
struct T(u8, f64, bool, String);
fn main() {
// serialization
let t = T(10u8, 3.14159, true, "Hello".to_owned());
let s = serde_json::to_string(&t).unwrap();
// `s` represented as `[10, 3.14159, true, "Hello"]`
println!("{}", s);
// deserialization
let d: T = serde_json::from_str(r#"[10, 3.14159, true, "Hello"]"#).unwrap();
// `d` is `T(10u8, 3.14159, true, "Hello".to_owned())`;
println!("{:?}", d);
}
// Output:
// [10,3.14159,true,"Hello"]
// T(10, 3.14159, true, "Hello")
Haskell Newtype-like structs
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Debug)]
struct N(i32);
fn main() {
// serialization
let n = N(10i32);
let s = serde_json::to_string(&n).unwrap();
// `s` represented as `10`
println!("{}", s);
// deserialization
let d: N = serde_json::from_str("10").unwrap();
// `d` is `N(10)`;
println!("{:?}", d);
}
// Output:
// 10
// N(10)
C-like structs
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Debug)]
struct C {
a: i32,
b: f64,
c: bool,
d: String,
}
fn main() {
// serialization
let t = C {
a: 10,
b: 3.14159,
c: true,
d: "Hello".to_owned(),
};
let s = serde_json::to_string(&t).unwrap();
// `s` represented as `{"a": 10, "b": 3.14159, "c": true, "d": "Hello"}`
println!("{}", s);
// deserialization
let d: C = serde_json::from_str(&s).unwrap();
println!("{:?}", d);
}
// Output:
// {"a":10,"b":3.14159,"c":true,"d":"Hello"}
// C { a: 10, b: 3.14159, c: true, d: "Hello" }
Optional values
The examples you have come across so far had structs with required values.
null
is a valid json value that is often used in cases data is missing.
Take for example the following:
#[derive(Serialize, Deserialize)]
struct C {
a: i32,
b: f64,
}
// serialization with missing value
let t = C {
b: 3.14159,
};
serde_json::to_string(&t).unwrap();
The above program would fail to compile since we've missed a
entirely
error[E0063]: missing field `a` in initializer of `C`
Deserialization with a null value for a
would cause errors.
Its type is i32 and we can't arbitrarily choose 0
to indicate absence of data.
In such cases, it's preferred that we mark the values as Optional as shown below.
use serde::{Serialize, Deserialize};
#[derive(Serialize, Deserialize, Debug)]
struct C {
a: Option<i32>,
b: f64,
}
fn main() {
// serialization with missing value
let t = C {
a: None,
b: 3.14159,
};
println!("{}", serde_json::to_string(&t).unwrap());
// `{"a": null, "b": 3.14159}`
// deserialization with missing value
let d: C = serde_json::from_str(r#"{"a": null, "b": 3.14159}"#).unwrap();
// `d` equals `C { a: None, b: 3.14159 }`
println!("{:?}", d);
println!("Hello, world!");
}
// Output:
// {"a":null,"b":3.14159}
// C { a: None, b: 3.14159 }
// Hello, world!
Default values
use serde::Deserialize;
#[derive(Deserialize, Debug)]
struct Test {
// Use default_str_fun() as the default if "my_str" is missing
#[serde(default = "default_str_fun")]
my_str: String,
// Use MyType::default if "my_type" is missing
#[serde(default)]
my_type: MyType,
}
fn default_str_fun() -> String {
"hi".to_owned()
}
/// Timeout in seconds.
#[derive(Deserialize, Debug)]
struct MyType(Vec<i32>);
impl std::default::Default for MyType {
fn default() -> Self {
Self(vec![1, 2, 3])
}
}
fn main() {
let d1: Test = serde_json::from_str(r#"{"my_type": [1]}"#).unwrap();
// `d1` equals `Test { my_str: "hi".to_owned(), my_type: MyType(vec![1]) }`
println!("{:?}", d1);
let d2: Test = serde_json::from_str(r#"{"my_str": "hello world"}"#).unwrap();
// `d2` equals `Test { my_str: "hello world".to_owned(), my_type: MyType(vec![1,2,3]) }`
println!("{:?}", d2);
println!("Hello, world!");
}
// Output:
// Test { my_str: "hi", my_type: MyType([1]) }
// Test { my_str: "hello world", my_type: MyType([1, 2, 3]) }
// Hello, world!
Missing field vs null value
A null
value isn't the same as a missing field altogether.
The following program panics on deserialization.
// ================
// BROKEN
// ================
use serde::Deserialize;
#[derive(Deserialize, Debug)]
struct Test {
// Use default_str_fun() as the default if "my_str" is missing
#[serde(default = "default_str_fun")]
my_str: String,
// Use MyType::default if "my_type" is missing
#[serde(default)]
my_type: MyType,
}
fn default_str_fun() -> String {
"hi".to_owned()
}
/// Timeout in seconds.
#[derive(Deserialize, Debug)]
struct MyType(Vec<i32>);
impl std::default::Default for MyType {
fn default() -> Self {
Self(vec![1, 2, 3])
}
}
fn main() {
let d: Test = serde_json::from_str(r#"{
"my_str": "hello world",
"my_type": null
}"#).unwrap();
println!("{:?}", d);
}
// Output:
// Running `target/debug/playground`
// thread 'main' panicked at 'called `Result::unwrap()` on an
// `Err` value: Error("invalid type: null, expected a sequence", line: 3, column: 23)',
// src/main.rs:32:10
// note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
json! macro
The json!
macro allows one to construct serde_json::Value
objects by hand.
let j: serde_json::Value = json!({
"a": 10u8,
"b": 3.14159,
"c": true,
"d": "Hello".to_owned(),
});
let s = j.to_string();
// `s` represented as `{"a": 10, "b": 3.14159, "c": true, "d": "Hello"}`
Deserializing to Map
// deserializing to Map<String, Value>
use serde_json::{Value, Map};
println!(
"{:?}",
serde_json::from_str::<Map<String, Value>>(r#"
{"gender":"male","blah":25}
"#).unwrap());
// {"blah": Number(25), "gender": String("male")}
enums
The default enum representation has the enum variant 'externally tagging' the content.
#[derive(Serialize, Deserialize)]
enum Color {
C { red: u8, green: u8, blue: u8 },
T(u8, u8, u8),
N(i32),
D,
}
let a = Color::C {
red: 10,
green: 100,
blue: 125
};
// `a` represented as `{"C":{"red":10,"green":100, "blue": 125}}`
let b = Color::T(10, 100, 125);
// `b` represented as `{"T":[10,100,125]}`
let c = Color::N(0);
// `c` represented as `{"N":0}`
let d = Color::D;
// `d` represented as `"D"`
It's also possible to delineate the tag and content separately in the object
next to each other
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize)]
#[serde(tag = "type", content = "body")]
enum Color {
C { red: u8, green: u8, blue: u8 },
T(u8, u8, u8),
N(i32),
D,
}
fn main() {
let a = Color::C {
red: 10,
green: 100,
blue: 125,
};
println!("{}", serde_json::to_string(&a).unwrap());
// {"type":"C","body":{"red":10,"green":100,"blue":125}}
}
Ommitting the content
should merge the tag with the rest of the content.
// ===============
// BROKEN PROGRAM
// ===============
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize)]
#[serde(tag = "type")]
enum Color {
C { red: u8, green: u8, blue: u8 },
T(u8, u8, u8),
N(i32),
D,
}
fn main() {
println!("Hello, world!");
let a = Color::C {
red: 10,
green: 100,
blue: 125,
};
println!("{}", serde_json::to_string(&a).unwrap());
}
Wait! The code fails to build. Let's look at the error message.
Compiling playground v0.0.1 (/playground)
error: #[serde(tag = "...")] cannot be used with tuple variants
--> src/main.rs:7:5
|
7 | T(u8, u8, u8),
| ^^^^^^^^^^^^^
error[E0277]: the trait bound `Color: Serialize` is not satisfied
--> src/main.rs:19:42
|
19 | println!("{}", serde_json::to_string(&a).unwrap());
| ^^ the trait `Serialize` is not implemented for `Color`
|
::: /playground/.cargo/registry/src/github.com-1ecc6299db9ec823/serde_json-1.0.61/src/ser.rs:2221:17
|
2221 | T: ?Sized + Serialize,
| --------- required by this bound in `serde_json::to_string`
error: aborting due to 2 previous errors
For more information about this error, try `rustc --explain E0277`.
error: could not compile `playground`
To learn more, run the command again with --verbose.
Removing the tuple variant makes it work again! I haven't really spent time figuring
out what's gone wrong here.
// Using just serde `tag` and no `content`
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize)]
#[serde(tag = "type")]
enum Color {
C { red: u8, green: u8, blue: u8 },
// commenting out the tuple variant
// T(u8, u8, u8),
N(i32),
D,
}
fn main() {
let a = Color::C {
red: 10,
green: 100,
blue: 125,
};
println!("{}", serde_json::to_string(&a).unwrap());
// {"type":"C","red":10,"green":100,"blue":125}
}
If you don't want any tag information creeping in the serialized string,
choose the untagged
representation. While deserializing, serde would try
each variant and returns the first one that succeeds.
// Untagged version
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize)]
#[serde(untagged)]
enum Color {
C { red: u8, green: u8, blue: u8 },
T(u8, u8, u8),
N(i32),
D,
}
fn main() {
let a = Color::C {
red: 10,
green: 100,
blue: 125,
};
println!("{}", serde_json::to_string(&a).unwrap());
// {"red":10,"green":100,"blue":125}
}
Aliasing and renaming fields
You can change the case for individual or all of the fields while serializing
to json representation.
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
struct Address {
id: i64,
street_name: String,
zip_code: String,
city: String,
}
let address = Address {
id: 1,
street_name: "ABC street".to_owned(),
zip_code: "10101010".to_owned(),
city: "XYZ City".to_owned(),
};
let js = serde_json::to_string(&address).unwrap();
// js represented as:
// `{"id": 1, "streetName": "ABC street", "zipCode": "10101010", "city": "XYZ City"}`
Fields can also be aliased by multiple names while deserializing the json string.
e.g. field attribute: #[serde(alias = "<Choose your field alias>")]
You can avoid serializing or deserializing a field. The attributes to consider are the following:
#[serde(skip_deserializing)]
, #[serde(skip_serializing)]
, #[serde(skip)]
#[derive(Serialize, Deserialize, Debug)]
struct XYZP {
#[serde(alias = "gender")]
#[serde(alias = "category")]
sex: String,
#[serde(rename = "blah")]
age_years: u8,
#[serde(skip)]
weight_kg: u8,
}
let xyzp = XYZP {
sex: "male".to_owned(),
age_years: 25,
weight_kg: 76,
};
println!("{}", serde_json::to_string(&xyzp).unwrap());
// {"sex":"male","blah":25}
println!(
"{:?}",
serde_json::from_str::<XYZP>(r#"
{"gender":"male","blah":25}
"#
).unwrap());
// XYZP { sex: "male", age_years: 25, weight_kg: 0 }
Posted on January 3, 2021
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.