实现复杂的自定义结构序列化程序
Implementing a complex custom struct serializer
我正在试验 Rust 并试图用 Rust 为 Elasticsearch 查询编写一个包装器。我已经实现了一个查询并且它工作正常,但是我真的不喜欢我使用 json!
宏的方式。
fn main() {
let actual = Query {
field: "field_name".into(),
values: vec![1, 2, 3],
boost: Some(2),
name: Some("query_name".into()),
};
let expected = serde_json::json!({
"terms": {
"field_name": [1, 2, 3],
"boost": 2,
"_name": "query_name"
}
});
let actual_str = serde_json::to_string(&actual).unwrap();
let expected_str = serde_json::to_string(&expected).unwrap();
assert_eq!(actual_str, expected_str);
}
#[derive(Debug)]
struct Query {
field: String,
values: Vec<i32>,
boost: Option<i32>,
name: Option<String>,
}
impl serde::Serialize for Query {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let field = self.field.as_str();
let values = self.values.as_slice();
let value = match (&self.boost, &self.name) {
(None, None) => serde_json::json!({ field: values }),
(None, Some(name)) => serde_json::json!({ field: values, "_name": name }),
(Some(boost), None) => serde_json::json!({ field: values, "boost": boost }),
(Some(boost), Some(name)) => {
serde_json::json!({ field: values, "boost": boost, "_name": name })
}
};
serde_json::json!({ "terms": value }).serialize(serializer)
}
}
我想知道如何使用 serde 的内置特征实现这样的序列化程序,例如 SerializeStruct
、SerializeMap
等。基本上我想避免使用 json 宏或创建中间数据结构。
如果你让你的数据结构更匹配 JSON 一点,那么你可以用几个 #[serde]
注释来完成整个序列化,这更易于维护,而且不太可能比手动实施慢。 (Edit:动态命名的字段实际上使这变得棘手,因此在这种情况下,Serialize
的自定义实现实际上可能是最好的。
Serde 序列化通过委托给 Serialize
的其他实现来工作,因此很难在单个实现中从平面结构创建自定义嵌套映射。
您可以像这样为嵌套创建一个临时结构:
// Need to import SerializeMap to call its methods
use serde::{Serialize, ser::SerializeMap};
// temporary wrapper for serializing Query as the inner object
struct InnerQuery<'a>(&'a Query);
impl<'a> serde::Serialize for InnerQuery<'a> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let query = self.0;
// We don't know the length of the map at this point, so it's None
let mut map = serializer.serialize_map(None)?;
map.serialize_entry(&query.field, &query.values)?;
if let Some(boost) = &query.boost {
map.serialize_entry("boost", boost)?;
}
if let Some(name) = &query.name {
map.serialize_entry("_name", name)?;
}
map.end()
}
}
这会给你里面的部分:
{
"field_name": [1, 2, 3],
"boost": 2,
"_name": "query_name"
}
然后实现主结构的序列化,例如:
impl serde::Serialize for Query {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let mut map = serializer.serialize_map(Some(1))?;
map.serialize_entry("terms", &InnerQuery(&self))?;
map.end()
}
}
请注意,键的顺序是不确定的,因此您的测试需要更改。一种方法是将其解析回 serde_json::Value
,它将按您的预期比较相等性:
let actual_str = serde_json::to_string(&actual).unwrap();
let actual: serde_json::Value = serde_json::from_str(&actual_str).unwrap();
assert_eq!(actual, expected);
如果想尽量避免人工执行,我觉得下面的方案也不错。您的要求很奇怪,所以我建议您避免这种要求。
我的解决方案可重复用于其他结构,CustomName
可以在很多上下文中使用,您只需要 flat 使用 serde 属性。
下面也只比较jsonValue
,这样比较好,因为json是无序的。
use serde::ser::SerializeMap; // 1.0.120
struct Query {
field: String,
values: Vec<i32>,
boost: Option<i32>,
name: Option<String>,
}
fn main() {
let actual = Query {
field: "field_name".into(),
values: vec![1, 2, 3],
boost: Some(2),
name: Some("query_name".into()),
};
let expected = serde_json::json!({
"terms": {
"field_name": [1, 2, 3],
"boost": 2,
"_name": "query_name"
}
});
let result = serde_json::to_value(&actual).unwrap();
assert_eq!(result, expected);
}
// the following concern serrialize implemenation
struct CustomName<'a, 'b> {
field: &'a str,
values: &'b [i32],
}
#[derive(serde::Serialize)]
struct SerializeQuery<'a, 'b, 'c, 'd> {
boost: Option<&'a i32>,
#[serde(rename = "_name")]
name: Option<&'b str>,
#[serde(flatten)]
custom_name: CustomName<'c, 'd>,
}
#[derive(serde::Serialize)]
struct Terms<'a, 'b, 'c, 'd> {
terms: SerializeQuery<'a, 'b, 'c, 'd>,
}
impl<'a> From<&'a Query> for Terms<'a, 'a, 'a, 'a> {
fn from(query: &'a Query) -> Self {
Self {
terms: SerializeQuery {
boost: query.boost.as_ref(),
name: query.name.as_deref(),
custom_name: CustomName {
field: &query.field,
values: &query.values,
},
},
}
}
}
impl serde::Serialize for Query {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
Terms::from(self).serialize(serializer)
}
}
impl<'a, 'b> serde::Serialize for CustomName<'a, 'b> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let mut map = serializer.serialize_map(Some(1))?;
map.serialize_entry(self.field, self.values)?;
map.end()
}
}
如您所见,我们在序列化实现方面做的不多,我不认为我们可以摆脱使用 serialize_map
因为字段名称是动态的。
我正在试验 Rust 并试图用 Rust 为 Elasticsearch 查询编写一个包装器。我已经实现了一个查询并且它工作正常,但是我真的不喜欢我使用 json!
宏的方式。
fn main() {
let actual = Query {
field: "field_name".into(),
values: vec![1, 2, 3],
boost: Some(2),
name: Some("query_name".into()),
};
let expected = serde_json::json!({
"terms": {
"field_name": [1, 2, 3],
"boost": 2,
"_name": "query_name"
}
});
let actual_str = serde_json::to_string(&actual).unwrap();
let expected_str = serde_json::to_string(&expected).unwrap();
assert_eq!(actual_str, expected_str);
}
#[derive(Debug)]
struct Query {
field: String,
values: Vec<i32>,
boost: Option<i32>,
name: Option<String>,
}
impl serde::Serialize for Query {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let field = self.field.as_str();
let values = self.values.as_slice();
let value = match (&self.boost, &self.name) {
(None, None) => serde_json::json!({ field: values }),
(None, Some(name)) => serde_json::json!({ field: values, "_name": name }),
(Some(boost), None) => serde_json::json!({ field: values, "boost": boost }),
(Some(boost), Some(name)) => {
serde_json::json!({ field: values, "boost": boost, "_name": name })
}
};
serde_json::json!({ "terms": value }).serialize(serializer)
}
}
我想知道如何使用 serde 的内置特征实现这样的序列化程序,例如 SerializeStruct
、SerializeMap
等。基本上我想避免使用 json 宏或创建中间数据结构。
如果你让你的数据结构更匹配 JSON 一点,那么你可以用几个 #[serde]
注释来完成整个序列化,这更易于维护,而且不太可能比手动实施慢。 (Edit:动态命名的字段实际上使这变得棘手,因此在这种情况下,Serialize
的自定义实现实际上可能是最好的。
Serde 序列化通过委托给 Serialize
的其他实现来工作,因此很难在单个实现中从平面结构创建自定义嵌套映射。
您可以像这样为嵌套创建一个临时结构:
// Need to import SerializeMap to call its methods
use serde::{Serialize, ser::SerializeMap};
// temporary wrapper for serializing Query as the inner object
struct InnerQuery<'a>(&'a Query);
impl<'a> serde::Serialize for InnerQuery<'a> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let query = self.0;
// We don't know the length of the map at this point, so it's None
let mut map = serializer.serialize_map(None)?;
map.serialize_entry(&query.field, &query.values)?;
if let Some(boost) = &query.boost {
map.serialize_entry("boost", boost)?;
}
if let Some(name) = &query.name {
map.serialize_entry("_name", name)?;
}
map.end()
}
}
这会给你里面的部分:
{
"field_name": [1, 2, 3],
"boost": 2,
"_name": "query_name"
}
然后实现主结构的序列化,例如:
impl serde::Serialize for Query {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let mut map = serializer.serialize_map(Some(1))?;
map.serialize_entry("terms", &InnerQuery(&self))?;
map.end()
}
}
请注意,键的顺序是不确定的,因此您的测试需要更改。一种方法是将其解析回 serde_json::Value
,它将按您的预期比较相等性:
let actual_str = serde_json::to_string(&actual).unwrap();
let actual: serde_json::Value = serde_json::from_str(&actual_str).unwrap();
assert_eq!(actual, expected);
如果想尽量避免人工执行,我觉得下面的方案也不错。您的要求很奇怪,所以我建议您避免这种要求。
我的解决方案可重复用于其他结构,CustomName
可以在很多上下文中使用,您只需要 flat 使用 serde 属性。
下面也只比较jsonValue
,这样比较好,因为json是无序的。
use serde::ser::SerializeMap; // 1.0.120
struct Query {
field: String,
values: Vec<i32>,
boost: Option<i32>,
name: Option<String>,
}
fn main() {
let actual = Query {
field: "field_name".into(),
values: vec![1, 2, 3],
boost: Some(2),
name: Some("query_name".into()),
};
let expected = serde_json::json!({
"terms": {
"field_name": [1, 2, 3],
"boost": 2,
"_name": "query_name"
}
});
let result = serde_json::to_value(&actual).unwrap();
assert_eq!(result, expected);
}
// the following concern serrialize implemenation
struct CustomName<'a, 'b> {
field: &'a str,
values: &'b [i32],
}
#[derive(serde::Serialize)]
struct SerializeQuery<'a, 'b, 'c, 'd> {
boost: Option<&'a i32>,
#[serde(rename = "_name")]
name: Option<&'b str>,
#[serde(flatten)]
custom_name: CustomName<'c, 'd>,
}
#[derive(serde::Serialize)]
struct Terms<'a, 'b, 'c, 'd> {
terms: SerializeQuery<'a, 'b, 'c, 'd>,
}
impl<'a> From<&'a Query> for Terms<'a, 'a, 'a, 'a> {
fn from(query: &'a Query) -> Self {
Self {
terms: SerializeQuery {
boost: query.boost.as_ref(),
name: query.name.as_deref(),
custom_name: CustomName {
field: &query.field,
values: &query.values,
},
},
}
}
}
impl serde::Serialize for Query {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
Terms::from(self).serialize(serializer)
}
}
impl<'a, 'b> serde::Serialize for CustomName<'a, 'b> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let mut map = serializer.serialize_map(Some(1))?;
map.serialize_entry(self.field, self.values)?;
map.end()
}
}
如您所见,我们在序列化实现方面做的不多,我不认为我们可以摆脱使用 serialize_map
因为字段名称是动态的。