serdeにおいてPhantomData型はデフォルトでは空の値として扱われ,例えば以下のVideo
型を
use serde::{Deserialize, Serialize};
use std::marker::PhantomData;
#[derive(Debug, Serialize, Deserialize, PartialEq)]
struct Video<T> {
file_type: PhantomData<T>,
file_name: String,
}
impl<T> Video<T> {
fn new(file_name: String) -> Video<T> {
Video {
file_name,
file_type: PhantomData,
}
}
}
#[derive(Debug, PartialEq)]
struct Mp4;
#[derive(Debug, PartialEq)]
struct Avi;
jsonにシリアライズした場合,file_type
はnull
になります.デシリアライズの際はnull
が任意のPhantomData
型になります.
let video = Video::<Mp4>::new("my video 1".to_string());
assert_eq!(
serde_json::to_string(&video).unwrap(),
r#"{"file_type":null,"file_name":"my video 1"}"#
);
let video_json = r#"{"file_type":null,"file_name":"my video 2"}"#;
assert_eq!(
serde_json::from_str::<Video<Avi>>(video_json).unwrap(),
Video::<Avi>::new("my video 2".to_string())
)
多くの場合はデフォルトの挙動が望ましいと思いますが,他の言語でも扱う場合などPhantomData
を文字列等の他の型へ変換したいこともあると思います.今回はPhantomData
を文字列としてシリアライズ・デシリアライズする方法を二つ紹介します.コード全体はこちら.
まずMp4
,Avi
などのUnitタイプにTryFrom<String>
とInto<String>
を実装します.Clone
トレイトは後者の方法のみで必要です.Default
トレイトはPhantomData
から文字列に変換するときに利用します.
#[derive(Clone, Debug, Default, PartialEq)]
struct Mp4;
impl TryFrom<String> for Mp4 {
type Error = String;
fn try_from(value: String) -> Result<Self, Self::Error> {
if value.as_str() == stringify!(Mp4) {
Ok(Mp4)
} else {
Err("Parse Error".to_string())
}
}
}
impl From<Mp4> for String {
fn from(_: Mp4) -> Self {
stringify!(Mp4).to_string()
}
}
#[derive(Clone, Debug, Default, PartialEq)]
struct Avi;
impl TryFrom<String> for Avi {
type Error = String;
fn try_from(value: String) -> Result<Self, Self::Error> {
if value.as_str() == stringify!(Avi) {
Ok(Avi)
} else {
Err("Parse Error".to_string())
}
}
}
impl From<Avi> for String {
fn from(_: Avi) -> Self {
stringify!(Avi).to_string()
}
}
serde_withを利用する
serde(serialize_with = "path")
とserde(deserialize_with = "path")
フィールドアトリビュートを指定することでシリアライズとデシリアライズを特定の関数で行うことができます.
以下の関数ではPhantomData<T>
をシリアライズ・デシリアライズしています.serialize_phantom
ではDefault
トレイトを使って与えた型パラメーターから値を作成,文字列に変換しています.deserialize_phantom
では与えた型パラメーターに文字列が変換できるかチェックしています.serde::de::Error::custom
ではDisplay
トレイトを実装した任意の値を渡してエラーを作成できます.
use serde::{Deserializer, Serializer};
fn serialize_phantom<S, T>(_: &PhantomData<T>, s: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
T: Into<String> + Default,
{
let type_str: String = T::default().into();
s.serialize_str(&type_str)
}
fn deserialize_phantom<'de, D, T>(d: D) -> Result<PhantomData<T>, D::Error>
where
D: Deserializer<'de>,
T: TryFrom<String>,
{
let unit_type_str: String = Deserialize::deserialize(d)?;
let _unit_type: T = unit_type_str
.try_into()
.map_err(|_| serde::de::Error::custom("Parse Error".to_string()))?;
Ok(PhantomData)
}
serdeのアトリビュートは以下のようにします.関数でシリアライズ・デシリアライズを行うため,型パラメーターのトレイト境界ももとのSerialize, Deserialize
からその関数に合うように変更します.
#[derive(Debug, Serialize, Deserialize, PartialEq)]
struct Video<T> {
#[serde(
serialize_with = "serialize_phantom",
deserialize_with = "deserialize_phantom"
)]
#[serde(bound(serialize = "T: Into<String> + Default"))]
#[serde(bound(deserialize = "T: TryFrom<String>"))]
file_type: PhantomData<T>,
file_name: String,
}
これによって,以下のように文字列としてシリアライズ・デシリアライズされます.
let video = Video::<Mp4>::new("my video 1".to_string());
assert_eq!(
serde_json::to_string(&video).unwrap(),
r#"{"file_type":"Mp4","file_name":"my video 1"}"#
);
let video_json = r#"{"file_type":"Avi","file_name":"my video 2"}"#;
assert_eq!(
serde_json::from_str::<Video<Avi>>(video_json).unwrap(),
Video::<Avi>::new("my video 2".to_string())
)
NewTypeパターンを使う
PhantomData
型をラップした新しい型SerdePhantom
を定義し,SerdePhantom
に文字列との変換用のトレイトを実装します.serde(try_from = "FromType")
とserde(into = "IntoType")
コンテナアトリビュートを指定することでシリアライズの前とデシリアライズの後に別の型を経由するようになります.serde(into = "IntoType")
を利用するにはClone
トレイトが必要となります.
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(try_from = "String", into = "String")]
#[serde(bound(serialize = "T: Default + Into<String> + Clone"))]
#[serde(bound(deserialize = "T: TryFrom<String>"))]
struct SerdePhantomData<T>(PhantomData<T>);
impl<T> TryFrom<String> for SerdePhantomData<T>
where
T: TryFrom<String>,
{
type Error = String;
fn try_from(value: String) -> Result<Self, Self::Error> {
let _unit_type: T = value.try_into().map_err(|_| "Parse Error".to_string())?;
Ok(SerdePhantomData(PhantomData))
}
}
impl<T> From<SerdePhantomData<T>> for String
where
T: Default + Into<String>,
{
fn from(_value: SerdePhantomData<T>) -> Self {
T::default().into()
}
}
Video
側でも型パラメーターのトレイト境界を変更します.
#[derive(Debug, PartialEq, Serialize, Deserialize)]
struct Video<T> {
#[serde(bound(serialize = "T: Default + Into<String> + Clone"))]
#[serde(bound(deserialize = "T: TryFrom<String>"))]
file_type: SerdePhantomData<T>,
file_name: String,
}
impl<T> Video<T> {
fn new(file_name: String) -> Video<T> {
Video {
file_name,
file_type: SerdePhantomData(PhantomData),
}
}
}
jsonとの変換は前者と全く同じように利用できます.
let video = Video::<Mp4>::new("my video 1".to_string());
assert_eq!(
serde_json::to_string(&video).unwrap(),
r#"{"file_type":"Mp4","file_name":"my video 1"}"#
);
let video_json = r#"{"file_type":"Avi","file_name":"my video 2"}"#;
assert_eq!(
serde_json::from_str::<Video<Avi>>(video_json).unwrap(),
Video::<Avi>::new("my video 2".to_string())
)