如何使用 serde 自定义结果类型的序列化?

huangapple go评论98阅读模式
英文:

How customize serialization of a result-type using serde?

问题

  1. #[derive(Serialize, Deserialize)]
  2. struct ServerResponse<D, E> {
  3. result: Result<D, E>,
  4. version: String,
  5. // More parameters here
  6. }

我想要能够将这个结构序列化和反序列化为以下格式。

如果结果成功

  1. {
  2. "result": "my_result_data",
  3. "version": "1.0"
  4. }

如果结果不成功

  1. {
  2. "error": { "message": "请求失败", "error-code": 212 },
  3. "version": "1.0"
  4. }
英文:

I have a struct which contains a Result field that I want to serialize

  1. #[derive(Serialize, Deserialize)]
  2. struct ServerResponse&lt;D,E&gt; {
  3. result : Result&lt;D,E&gt;,
  4. version : String,
  5. // More parameters here
  6. }

I want to be able to serialize and deserialize this struct to the following-format.

If result is successful

  1. {
  2. &quot;result&quot; : &quot;my_result_data&quot;,
  3. &quot;version&quot; : &quot;1.0&quot;,
  4. }

If the result is not successful

  1. {
  2. &quot;error&quot; : { &quot;message&quot; : &quot;The request failed&quot;, &quot;error-code&quot; : 212 },
  3. &quot;version&quot; : &quot;1.0&quot;
  4. }

答案1

得分: 1

我对你的问题有一个疑问:你希望从一个包含 messageerror-code 字段的 JSON 对象中反序列化 Err,然而它是一个泛型参数,并且没有指示它受到任何特定类型的约束。

我选择通过定义一个新的 trait 来解决这个问题:

  1. pub trait ResponseError {
  2. fn message(&self) -> &str;
  3. fn error_code(&self) -> i32;
  4. fn from_parts(message: &str, code: i32) -> Self;
  5. }

任何想要被序列化的 ServerResponse 必须满足 E: ResponseError。这可能完全是错误的做法,但根据我所了解的有限信息,这是我能做的最好的事情。

实际的实现相当机械:

  1. 设置 ServerResponse 委托序列化和反序列化给一组自定义函数:
  1. #[derive(Serialize, Deserialize, Debug)]
  2. #[serde(bound(
  3. serialize = "D: Serialize, E: ResponseError",
  4. deserialize = "D: DeserializeOwned, E: ResponseError"
  5. ))]
  6. struct ServerResponse<D, E> {
  7. #[serde(with = "untagged_ok_result")]
  8. result: Result<D, E>,
  9. version: String,
  10. // 更多参数在这里
  11. }
  1. 搭建这些函数的框架:
  1. mod untagged_ok_result {
  2. pub fn serialize<'a, S, T, E>(v: &Result<T, E>, ser: S) -> Result<S::Ok, S::Error> { todo!() }
  3. pub fn deserialize<'de, D, T, E>(de: D) -> Result<Result<T, E>, D::Error> { todo!() }
  4. }
  1. 定义一个 Rust 类型,其中派生的 De/Serialize 匹配实际的 JSON 格式:
  1. #[derive(Serialize, Deserialize)]
  2. #[serde(untagged)]
  3. #[serde(bound(deserialize = "D: Deserialize<'a>"))]
  4. enum Mresult<'a, D> {
  5. Ok(D),
  6. Err {
  7. #[serde(borrow)]
  8. message: Cow<'a, str>,
  9. #[serde(rename = "error-code")]
  10. error_code: i32,
  11. },
  12. }
  1. 通过在 stdResult 和自定义类型之间进行转换来实现序列化和反序列化:
  1. pub fn serialize<'a, S, T, E>(v: &Result<T, E>, ser: S) -> Result<S::Ok, S::Error>
  2. where
  3. S: Serializer,
  4. T: Serialize,
  5. E: ResponseError,
  6. {
  7. match v {
  8. Ok(v) => Mresult::Ok(v),
  9. Err(v) => Mresult::Err {
  10. message: v.message().into(),
  11. error_code: v.error_code(),
  12. },
  13. }
  14. .serialize(ser)
  15. }
  16. pub fn deserialize<'de, D, T, E>(de: D) -> Result<Result<T, E>, D::Error>
  17. where
  18. D: Deserializer<'de>,
  19. T: Deserialize<'de>,
  20. E: ResponseError,
  21. {
  22. Ok(match Mresult::deserialize(de)? {
  23. Mresult::Ok(v) => Ok(v),
  24. Mresult::Err {
  25. message,
  26. error_code,
  27. } => Err(ResponseError::from_parts(&message, error_code)),
  28. })
  29. }
英文:

I have one issue with your question: You want Err to be deserialized from a json object with the fields message and error-code, yet it's a generic parameter and there's no indication that it is constrained to any specific type.

I've chosen to work around that issue by defining a new trait:

  1. pub trait ResponseError {
  2. fn message(&amp;self) -&gt; &amp;str;
  3. fn error_code(&amp;self) -&gt; i32;
  4. fn from_parts(message: &amp;str, code: i32) -&gt; Self;
  5. }

Any ServerResponse that wants to be serialized must E: ResponseError. That might entirely be the wrong thing to do, but with what limited information I have, it's the best thing I can do.

The actual implementation is quite mechanic:

  1. Set up ServerResponse to delegate de/serialization to a set of custom functions:

    1. #[derive(Serialize, Deserialize, Debug)]
    2. #[serde(bound(
    3. serialize = &quot;D: Serialize, E: ResponseError&quot;,
    4. deserialize = &quot;D: DeserializeOwned, E: ResponseError&quot;
    5. ))]
    6. struct ServerResponse&lt;D, E&gt; {
    7. #[serde(with = &quot;untagged_ok_result&quot;)]
    8. result: Result&lt;D, E&gt;,
    9. version: String,
    10. // More parameters here
    11. }
  2. Scaffold said functions

    1. mod untagged_ok_result {
    2. pub fn serialize&lt;&#39;a, S, T, E&gt;(v: &amp;Result&lt;T, E&gt;, ser: S) -&gt; Result&lt;S::Ok, S::Error&gt; { todo!() }
    3. pub fn deserialize&lt;&#39;de, D, T, E&gt;(de: D) -&gt; Result&lt;Result&lt;T, E&gt;, D::Error&gt; { todo!() }
    4. }
  3. Define a Rust type where derived De/Serialize matches whatever actual JSON format you want

    1. #[derive(Serialize, Deserialize)]
    2. #[serde(untagged)]
    3. #[serde(bound(deserialize = &quot;D: Deserialize&lt;&#39;a&gt;&quot;))]
    4. enum Mresult&lt;&#39;a, D&gt; {
    5. Ok(D),
    6. Err {
    7. #[serde(borrow)]
    8. message: Cow&lt;&#39;a, str&gt;,
    9. #[serde(rename = &quot;error-code&quot;)]
    10. error_code: i32,
    11. },
    12. }
  4. Implement de/serialization by converting between the std Result and the custom type:

    1. pub fn serialize&lt;&#39;a, S, T, E&gt;(v: &amp;Result&lt;T, E&gt;, ser: S) -&gt; Result&lt;S::Ok, S::Error&gt;
    2. where
    3. S: Serializer,
    4. T: Serialize,
    5. E: ResponseError,
    6. {
    7. match v {
    8. Ok(v) =&gt; Mresult::Ok(v),
    9. Err(v) =&gt; Mresult::Err {
    10. message: v.message().into(),
    11. error_code: v.error_code(),
    12. },
    13. }
    14. .serialize(ser)
    15. }
    16. pub fn deserialize&lt;&#39;de, D, T, E&gt;(de: D) -&gt; Result&lt;Result&lt;T, E&gt;, D::Error&gt;
    17. where
    18. D: Deserializer&lt;&#39;de&gt;,
    19. T: Deserialize&lt;&#39;de&gt;,
    20. E: ResponseError,
    21. {
    22. Ok(match Mresult::deserialize(de)? {
    23. Mresult::Ok(v) =&gt; Ok(v),
    24. Mresult::Err {
    25. message,
    26. error_code,
    27. } =&gt; Err(ResponseError::from_parts(&amp;message, error_code)),
    28. })
    29. }

Playground

(I was tempted to use the from/into attributes for a while, but those are container attributes, which causes other boilerplate. It might be an option for you anyway)

huangapple
  • 本文由 发表于 2023年7月3日 20:21:19
  • 转载请务必保留本文链接:https://go.coder-hub.com/76604700.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定