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 のフィールド属性の種類は下記の公式ページを参照してみてください。 (あまり親切ではないですが)




