みおもん倶楽部 技術雑記

世界の片隅で、諜報活動と称して本を読んだりゲームをしたり

Rustのserdeを使ってみる

serdeはserializeとdeserializeを行うクレートです。

関数を呼んで機能を実現する「わかりやすいライブラリ」ではなく、インターフェースというか、プロトコルというか、1段メタな機能を実現するツールであるように思います。attributeで目印をつけ、たとえばserde_jsonという別クレートで実際のシリアライズ・デシリアライズを行うと理解しています。

以下、jsonとstructの相互変換を行う例を雑にまとめてみます。

必要クレート

serde = { version = "1.0.130", features = ["derive"] }
serde_json = "1.0.68"

準備

use serde::{Deserialize, Serialize};
use std::fs;

#[derive(Serialize, Deserialize, Debug)]
struct Person {
    name: String,
    age: u8,
}

structからJSON

serde_json::to_writer(&mut file, &p)

JSONからstruct

serde_json::from_reader::<std::fs::File, Person>

もしくは

serde_json::from_str::<Person>(&content)

全体

use serde::{Deserialize, Serialize};
use std::fs;

const JSON_FILE_NAME: &str = "person.json";

#[derive(Serialize, Deserialize, Debug)]
struct Person {
    name: String,
    age: u8,
}

fn gen_json() {
    let json = r#"
        {
            "name": "John Doe",
            "age": 43
        }
    "#;

    let p: Person = serde_json::from_str(json).unwrap();
    // save to file
    let mut file = std::fs::File::create(JSON_FILE_NAME).unwrap();
    serde_json::to_writer(&mut file, &p).unwrap();
    println!("{:?}", p);
}

fn read_json_fs() {
    let file = std::fs::File::open(JSON_FILE_NAME).unwrap();

    match serde_json::from_reader::<std::fs::File, Person>(file) {
        Ok(p) => println!("{:?}", p),
        Err(e) => println!("error: {}", e),
    }
}

fn read_json_str() {
    let content = fs::read_to_string(JSON_FILE_NAME).unwrap();

    match serde_json::from_str::<Person>(&content) {
        Ok(p) => println!("{:?}", p),
        Err(e) => println!("error: {}", e),
    }
}

fn main() {
    // gen_json();
    read_json_fs();
    read_json_str();
}

結果のJSON

{"name":"John Doe","age":43}

JSONの値を不正にしてみる

{"name":"John Doe","age":1000}

注:Personのageはu8

fn main() {
    // gen_json();
    read_json_fs();
    read_json_str();
}

結果

error: invalid value: integer `1000`, expected u8 at line 1 column 30
error: invalid value: integer `1000`, expected u8 at line 1 column 29

上記、jsonとstructの例でしたが、例えばHashMapとstructではこうもいかないと思います。 なぜかというと、HashMapはkeyとvalueが静的に決まっている必要があり、nameを取得したらStringだけど、ageを取得したらu8、というような表現力がHashMapにはないからです。(というかそういうことをしたいならstruct)

あるいはHashMapに全部stringで入れて(u8も"20"などのようにStringで入れる)、structと相互変換する、も方法としてはアリかもしれませんが、まあだったらJSONでいいのでは、と思っています。

何を思ってこんなことを書いているかというと、なんらかのデータをデータベースから取得し、一時的にHashMapに入れ、structに変換するのはアリなのか?と思ったのですが、HashMapよりはjsonなどの方が取り回ししやすそうだね、と思った次第です。

無論、データベースから値を取るならそれ用のクレートを使うべきです。(serde_dynamodbなど)