Dependence

To handle JSON, let’s first add serde and serde_json to your project.

cargo add serde --features derive
cargo add serde_json

Or add the following to your Cargo.toml:

[dependencies]
serde = { version = "x.x.x", features = ["derive"] }
serde_json = "x.x.x"

Let’s get started

First, let’s envision a scenario where we validate incoming JSON data. For example, let’s say we want data that meets the following requirements.

  • name field that is not empty.
  • age field that falls within the range of 18 to 80.
  • friends field containing at least one entry, each with a name that is not empty.

We can define the rules for each field as follows:

use refined_type::rule::{MinMaxU8, NonEmptyString, NonEmptyVec};
use serde::Deserialize;

#[derive(Debug, Deserialize)]
struct Data {
    name: NonEmptyString,
    age: MinMaxU8<18, 80>,
    friends: NonEmptyVec<NonEmptyString>,
}

Valid JSON case

Now, let’s try to deserialize the valid JSON data.

fn main() {
    let json = r#"{
        "name": "john",
        "age": 55,
        "friends": ["tom", "hanako"]
    }"#;
    let my_struct = serde_json::from_str::<Data>(json);
    println!("{:?}", my_struct);
}
Ok(Data { name: Refined { value: "john" }, age: Refined { value: 55 }, friends: Refined { value: [Refined { value: "tom" }, Refined { value: "hanako" }] } })

The JSON data meets the requirements, so it is successfully deserialized.

Invalid JSON case

Next, let’s try to deserialize JSON data that does not meet the requirements.

name field is empty

fn main() {
    let json = r#"{
        "name": "",
        "age": 55,
        "friends": ["tom", "hamako"]
    }"#;
    let my_struct = serde_json::from_str::<Data>(json);
    println!("{:?}", my_struct);
}
$ cargo run
Err(Error("\"\" does not satisfy Not<refined_type::rule::empty::EmptyRule<alloc::string::String>>", line: 2, column: 18))

age field is out of range

fn main() {
    let json = r#"{
        "name": "john",
        "age": 10,
        "friends": ["tom", "hanako"]
    }"#;
    let my_struct = serde_json::from_str::<Data>(json);
    println!("{:?}", my_struct);
}
$ cargo run
Err(Error("[the value must be equal to 18, but received 10 || the value must be greater than 18, but received 10]", line: 3, column: 17))

friends field is empty

fn main() {
    let json = r#"{
        "name": "john",
        "age": 55,
        "friends": []
    }"#;
    let my_struct = serde_json::from_str::<Data>(json);
    println!("{:?}", my_struct);
}
$ cargo run
Err(Error("[] does not satisfy Not<refined_type::rule::empty::EmptyRule<alloc::vec::Vec<refined_type::refined::Refined<Not<refined_type::rule::empty::EmptyRule<alloc::string::String>>>>>>", line: 5, column: 5))

friends field is not empty, but the name is empty

fn main() {
    let json = r#"{
        "name": "john",
        "age": 55,
        "friends": ["tom", ""]
    }"#;
    let my_struct = serde_json::from_str::<Data>(json);
    println!("{:?}", my_struct);
}
$ cargo run
Err(Error("\"\" does not satisfy Not<refined_type::rule::empty::EmptyRule<alloc::string::String>>", line: 4, column: 30))

Summary

By defining rules for each field, you can easily validate JSON data. This is just one example of how you can use refined_type to enhance your types.

Enjoy a wonderful type life!