如何为计时时间戳使用自定义 serde 反序列化器?

Posted

技术标签:

【中文标题】如何为计时时间戳使用自定义 serde 反序列化器?【英文标题】:How to use a custom serde deserializer for chrono timestamps? 【发布时间】:2019-12-28 02:04:14 【问题描述】:

我正在尝试将 JSON 解析为具有 chrono::DateTime 字段的结构。 JSON 中的时间戳以我为其编写了反序列化器的自定义格式保存。

如何连接两者并使用#[serde(deserialize_with)] 使其工作?

我使用NaiveDateTime 来编写更简单的代码

extern crate serde;
extern crate serde_json;
use serde::Deserialize;
extern crate chrono;
use chrono::NaiveDateTime;

fn from_timestamp(time: &String) -> NaiveDateTime 
    NaiveDateTime::parse_from_str(time, "%Y-%m-%dT%H:%M:%S.%f").unwrap()


#[derive(Deserialize, Debug)]
struct MyJson 
    name: String,
    #[serde(deserialize_with = "from_timestamp")]
    timestamp: NaiveDateTime,


fn main() 
    let result: MyJson =
        serde_json::from_str(r#""name": "asdf", "timestamp": "2019-08-15T17:41:18.106108""#)
            .unwrap();
    println!(":?", result);

我得到三个不同的编译错误:

error[E0308]: mismatched types
  --> src/main.rs:11:10
   |
11 | #[derive(Deserialize, Debug)]
   |          ^^^^^^^^^^^ expected reference, found type parameter
   |
   = note: expected type `&std::string::String`
              found type `__D`

error[E0308]: mismatched types
  --> src/main.rs:11:10
   |
11 | #[derive(Deserialize, Debug)]
   |          ^^^^^^^^^^-
   |          |         |
   |          |         this match expression has type `chrono::NaiveDateTime`
   |          expected struct `chrono::NaiveDateTime`, found enum `std::result::Result`
   |          in this macro invocation
   |
   = note: expected type `chrono::NaiveDateTime`
              found type `std::result::Result<_, _>`

error[E0308]: mismatched types
  --> src/main.rs:11:10
   |
11 | #[derive(Deserialize, Debug)]
   |          ^^^^^^^^^^-
   |          |         |
   |          |         this match expression has type `chrono::NaiveDateTime`
   |          expected struct `chrono::NaiveDateTime`, found enum `std::result::Result`
   |          in this macro invocation
   |
   = note: expected type `chrono::NaiveDateTime`
              found type `std::result::Result<_, _>`

我很确定from_timestamp 函数返回的是DateTime 结构而不是Result,所以我不知道“预期结构chrono::NaiveDateTime,找到枚举std::result::Result”可能意味着什么。

【问题讨论】:

【参考方案1】:

这是相当复杂的,但以下工作:

use chrono::NaiveDateTime;
use serde::de;
use serde::Deserialize;
use std::fmt;

struct NaiveDateTimeVisitor;

impl<'de> de::Visitor<'de> for NaiveDateTimeVisitor 
    type Value = NaiveDateTime;

    fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result 
        write!(formatter, "a string represents chrono::NaiveDateTime")
    

    fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>
    where
        E: de::Error,
    
        match NaiveDateTime::parse_from_str(s, "%Y-%m-%dT%H:%M:%S.%f") 
            Ok(t) => Ok(t),
            Err(_) => Err(de::Error::invalid_value(de::Unexpected::Str(s), &self)),
        
    


fn from_timestamp<'de, D>(d: D) -> Result<NaiveDateTime, D::Error>
where
    D: de::Deserializer<'de>,

    d.deserialize_str(NaiveDateTimeVisitor)


#[derive(Deserialize, Debug)]
struct MyJson 
    name: String,
    #[serde(deserialize_with = "from_timestamp")]
    timestamp: NaiveDateTime,


fn main() 
    let result: MyJson =
        serde_json::from_str(r#""name": "asdf", "timestamp": "2019-08-15T17:41:18.106108""#)
            .unwrap();
    println!(":?", result);

【讨论】:

谢谢。那么我的实现问题与预期的函数签名不完全匹配? (fn from_timestamp&lt;'de, D&gt;(d: D) -&gt; Result&lt;NaiveDateTime, D::Error&gt; where D: de::Deserializer&lt;'de&gt;,) 这是问题的一部分,是的。但这只是胶水。您可能会注意到真正的关键在于访问者特征的实现。这就是自定义序列化器/反序列化器在 serde 中的工作方式。【参考方案2】:

虽然@edwardw 的回答在技术上是正确的,但恕我直言,它包含太多样板。

NaiveDataTime 实现了FromStr,这意味着您可以编写一个可重用的通用反序列化函数。

一个复杂的例子 - 确实添加了在 JSON 中表示为字符串的 age 字段 (u8)。只是为了证明您可以将它用于任何实现 FromStr 的东西。

use std::fmt::Display;
use std::str::FromStr;

use chrono::NaiveDateTime;
use serde::de, Deserialize, Deserializer;

#[derive(Deserialize, Debug)]
struct MyJson 
    name: String,
    #[serde(deserialize_with = "deserialize_from_str")]
    timestamp: NaiveDateTime,
    #[serde(deserialize_with = "deserialize_from_str")]
    age: u8,


// You can use this deserializer for any type that implements FromStr
// and the FromStr::Err implements Display
fn deserialize_from_str<'de, S, D>(deserializer: D) -> Result<S, D::Error>
where
    S: FromStr,      // Required for S::from_str...
    S::Err: Display, // Required for .map_err(de::Error::custom)
    D: Deserializer<'de>,

    let s: String = Deserialize::deserialize(deserializer)?;
    S::from_str(&s).map_err(de::Error::custom)


fn main() 
    let result: MyJson = serde_json::from_str(
        r#""name": "asdf", "timestamp": "2019-08-15T17:41:18.106108", "age": "11""#,
    )
    .unwrap();
    println!(":?", result);

如果你想指定格式(使用NaiveDateTime::parse_from_str)会更容易:

use chrono::NaiveDateTime;
use serde::de, Deserialize, Deserializer;

#[derive(Deserialize, Debug)]
struct MyJson 
    name: String,
    #[serde(deserialize_with = "naive_date_time_from_str")]
    timestamp: NaiveDateTime,


fn naive_date_time_from_str<'de, D>(deserializer: D) -> Result<NaiveDateTime, D::Error>
where
    D: Deserializer<'de>,

    let s: String = Deserialize::deserialize(deserializer)?;
    NaiveDateTime::parse_from_str(&s, "%Y-%m-%dT%H:%M:%S.%f").map_err(de::Error::custom)


fn main() 
    let result: MyJson =
        serde_json::from_str(r#""name": "asdf", "timestamp": "2019-08-15T17:41:18.106108""#)
            .unwrap();
    println!(":?", result);

#[serde(deserialize_with = "path")] 文档:

使用不同于Deserialize 实现的函数反序列化该字段。给定的函数必须可以作为fn&lt;'de, D&gt;(D) -&gt; Result&lt;T, D::Error&gt; where D: Deserializer&lt;'de&gt; 调用,尽管它也可能是T 的通用函数。与deserialize_with 一起使用的字段不需要实现Deserialize

【讨论】:

以上是关于如何为计时时间戳使用自定义 serde 反序列化器?的主要内容,如果未能解决你的问题,请参考以下文章

使用 Play Framework JSON 库的自定义 Joda 时间序列化器?

如何为自定义输入迭代器定义指针

如何为 PyYAML 编写表示器?

使用 Jackson 自定义反序列化:扩展默认反序列化器

是否可以在春季默认使用自定义序列化器/反序列化器?

Spring @RestController 自定义 JSON 反序列化器