Rust で serde を使った JSON シリアライズ時にフィールドの値を変換する
Rust でオブジェクトから JSON にシリアライズするときは serde と serde_json を使うのが一般的です。
今回はシリアライズするときに特定のフィールドの値を任意に変換する方法を紹介します。
事前準備
dependencies の追加
Cargo.toml の dependencies に serde と serde_json を追加します。
[dependencies]
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
構造体の準備
既存のシリアライズ可能な構造体を使う場合を除き、自分でシリアライズ可能な構造体を定義するときは derive
属性で Serialize
Trait を継承させます。
今回は String
型の topic
と f32
型の value
をもつ MyData
構造体を想定します。
use serde::Serialize;
#[derive(Serialize)]
struct MyData {
pub topic: String,
pub value: f32,
}
JSON へシリアライズ
単純なシリアライズ
JSON へシリアライズするには serde_json::to_string()
を呼び出します。
fn main() {
let data = MyData {
topic: "My first topic".to_string(),
value: 1.23456,
};
let content = serde_json::to_string(&data).unwrap();
println!("{}", content);
// will get `{"topic":"My first topic","value":1.23456}`
}
これで {"topic":"My first topic","value":1.23456}
のような JSON が得られるはずです。
なお、整形された JSON を得るには to_string
の代わりに to_string_pretty
を使います。
let content = serde_json::to_string_pretty(&data).unwrap();
シリアライズ関数を指定したシリアライズ
さて、本題のシリアライズ時に特定のフィールドを変換したい場合です。
そのときは変換用関数を作り、構造体のフィールドに対して serialize_with
を指定します。たとえば value
フィールドの変換に format_f32_data
という関数を用いる場合は下記のように構造体を宣言します。
use serde::Serialize;
#[derive(Serialize)]
struct MyData {
pub topic: String,
#[serde(serialize_with="format_f32_data")]
pub value: f32,
}
#[serde(serialize_with="format_f32_data")]
がこのフィールドの変換関数の指定です。
そして変換関数自体を実装します。シグネチャは fn function_name<S>(x: &f32, s: S) -> Result<S::Ok, S::Error> where S: Serializer
のようになります。少し複雑ですが、こういうものなのであまり悩まず、 function_name
f32
だけを必要な名前、型に変更して使いましょう。
下記は入力の浮動小数点数を小数点以下 2 桁で四捨五入する関数の例です。
fn format_f32_data<S>(x: &f32, s: S) -> Result<S::Ok, S::Error> where S: Serializer
{
s.serialize_f32((x * 100.0).round() / 100.0)
}
これで同様にシリアライズすると {"topic":"My first topic","value":1.23}
のように四捨五入された値で取得できるようになります。
ただ、変換が一方通行ならいいですが、この関数の場合は不可逆変換なので、デシリアライズすると結果が違ってきてしまいます。シリアライズ/デシリアライズ両方向の場合は、気をつけてください。
おまけ
serde でのデフォルト値の指定
Deserialize を行う構造体で、変換元の JSON にキーが含まれていないとき、デフォルト値を使いたい場合があります。
そんなときはフィールドと同じ型を返す関数を定義し、 #[serde(default="funcation_name")]
を指定しましょう。
fn default_as_true() -> bool { true } // true を返す関数
fn default_as_1() -> i32 { 1 } // 1 を返す関数
#[derive(Deserialize)]
struct Config {
#[serde(default="default_as_true")]
pub my_boolean: bool,
#[serde(default="default_as_1")]
pub my_integer: i32,
}
これだけです。少し面倒な気もしますが、関数で書ける分、リテラルより融通が利きます。
あとがき
ちなみに今回は JSON シリアライズを前提に書きましたが、 serde 共通なのでシリアライズ方法が JSON でなくとも大丈夫です。
今回紹介した serialize_with
や default
を含め、 serde のフィールド属性の種類は下記の公式ページを参照してみてください。 (あまり親切ではないですが)