我试图以一种具有挑战性的方式处理来自WebSocket的JSON格式,以便Serde进行反序列化。这个API有很多可能的响应,但在顶层,它总是至少有三个值。第一个是某个ID,第二个是作为对象的数据,第三个是对象的类型。
下面是一些JSON示例
[
32,
{
"speed": 900,
"altitude": 30000
"num_passengers": 200
},
"plane"
]
[
42,
{
"num_ingredients": 12,
"cook_time": "4 mins",
"oven_temp": 180
},
{
"num_ingredients": 4,
"cook_time": "25 mins",
"oven_temp": 250
},
"recipe"
]
我希望能够将其反序列化为enum。
enum Messages {
Plane(Plane),
Recipe(Recipe),
}
实际上,消息类型远远不止两种(大约有20种),我预计会收到相当大量的消息。由于这个原因,我有点担心使用无标记enum的性能。是否有其他解决方案来使用这种结构反序列化数据?
这是我想到的解决方案。
将目标结构定义为normal
#[derive(Debug, Serialize, Deserialize)]
struct Plane {
speed: u32,
altitude: i32,
num_passengers: u16
}
#[derive(Debug, Serialize, Deserialize)]
struct Recipe {
num_ingredients: u32,
cook_time: String,
oven_temp: u16
}
type Recipes = Vec<Recipe>;
则需要两个枚举。一个用来保存数据,另一个用来描述类型。注意到保存数据的一个有一个辅助函数(new
)。这将创建自身的一个实例基于类型定义和JSON值
#[derive(Debug, Serialize, Deserialize, EnumDisplay)]
#[serde(rename_all = "camelCase")]
enum MessageType {
Plane,
Recipe
}
#[derive(Debug, Serialize)]
enum Message {
Plane(Plane),
Recipe(Recipes)
}
impl Message {
fn new(message_type: &MessageType, message: serde_json::Value) -> Result<Message, serde_json::Error> {
Ok(match message_type {
MessageType::Plane => Self::Plane(serde_json::from_value(message)?),
MessageType::Recipe => Self::Recipe(serde_json::from_value(message)?),
})
}
}
最后一部分是包装器结构体。这将保存消息id和数据。这是需要自定义反序列化器的结构体,所以不要派生Deserialize
。
#[derive(Debug, Serialize)]
struct MessageWrapper {
id: i64,
message: Message,
}
现在是有趣的部分。自定义反序列化实现。
一步一步,这是它的工作。
- 解析第一个值(id)作为
i64
- 循环遍历下一个值,直到找到消息类型
- 如果值是字符串,则必须是消息类型,并且可以停止循环
- 如果值是一个对象或数组,将其添加到我们的消息列表
- 将数组值转换为单个值
- 如果数组中只有一个元素,删除它,那就是值
- 如果有多个内容,则使用值数组 构造一个新的Array类型值
- 使用我们在
Messsage
类型上创建的new
函数来解析消息 - 构造并返回一个新的
MessageWrapper
impl<'de> Deserialize<'de> for MessageWrapper {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
struct MessageWrapperVisitor {}
impl<'de> Visitor<'de> for MessageWrapperVisitor {
type Value = MessageWrapper;
fn expecting(&self, formatter: &mut Formatter) -> std::fmt::Result {
formatter.write_str("[i64, Message..., String]")
}
fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
where
A: SeqAccess<'de>,
{
let id: i64 = seq
.next_element()?
.ok_or_else(|| DeError::invalid_length(0, &self))?;
let message_type: MessageType;
let mut messages = Vec::with_capacity(1);
loop {
let val: serde_json::Value = seq
.next_element()?
.ok_or_else(|| DeError::invalid_length(1, &self))?;
if val.is_string() {
message_type = serde_json::from_value(val).or_else(|e| Err(DeError::custom(e)))?;
break;
} else if val.is_object() || val.is_array() {
messages.push(val);
} else {
return Err(DeError::custom("unexpected value. Expected channel name or json object"));
}
}
if messages.is_empty() {
return Err(DeError::custom("no data"));
}
let message: serde_json::Value;
if messages.len() == 1 {
message = messages.remove(0);
} else {
message = serde_json::Value::Array(messages)
}
let message = Message::new(&message_type, message)
.or_else(|e| Err(DeError::custom(format!("inner object cannot be deserialized as {} -> {}", message_type, e))))?;
Ok(MessageWrapper {
id,
message,
})
}
}
deserializer.deserialize_seq(MessageWrapperVisitor {})
}
}