9 个版本 (5 个重大更改)

0.6.0 2024 年 5 月 25 日
0.5.7 2024 年 3 月 12 日
0.4.0 2024 年 1 月 9 日
0.3.1 2023 年 11 月 26 日
0.0.2 2023 年 9 月 28 日

#59 in Web编程

Download history 576/week @ 2024-05-01 714/week @ 2024-05-08 1093/week @ 2024-05-15 1075/week @ 2024-05-22 1292/week @ 2024-05-29 1571/week @ 2024-06-05 1189/week @ 2024-06-12 895/week @ 2024-06-19 596/week @ 2024-06-26 803/week @ 2024-07-03 994/week @ 2024-07-10 2078/week @ 2024-07-17 4194/week @ 2024-07-24 4112/week @ 2024-07-31 2698/week @ 2024-08-07 1721/week @ 2024-08-14

每月 13,615 次下载
用于 7 个 Crates (5 个直接使用)

MIT/Apache

1MB
19K SLoC

biome_deserialize

biome_deserialize 包含知道如何反序列化自己的数据结构,以及知道如何反序列化数据的数据格式。它提供了一个框架,这两个群体可以相互交互,允许使用任何支持的数据格式反序列化任何支持的数据结构。

此 Crate 受 serde 启发。主要区别是 biome_deserialize 的容错行为。《Serde》使用快速失败策略,而 biome_deserialize 尽可能多地反序列化并报告多个诊断(错误、警告、弃用消息等)。此外,biome_deserialize 旨在反序列化文本数据格式。

biome_deserialize 假设每个支持的数据格式都支持以下类型

  • 类似null的值;
  • 布尔值;
  • 数字 - 整数和浮点数;
  • 字符串;
  • 数组;
  • 键值对映射(包括对象)。

它目前支持 JSON 数据格式。

设计概述

最重要的两个特性是 DeserializableDeserializableValue

  • 实现 Deserializable 的类型是可以从任何支持的数据格式反序列化的数据结构;
  • 实现 DeserializableValue 的类型是可以反序列化任何支持的数据结构的格式。

Deserializable 的简单实现可以重用其他可反序列化的数据结构。例如,对应于 A、B 和 C 之间的字符串的枚举,可以先反序列化一个字符串,然后检查该字符串是否是其值之一。

无法直接使用其他可序列化数据结构的数据结构,可以使用访问者模式。访问者通常是一个零大小的数据结构,它实现了 DeserializationVisitor 特性。访问者模式是一种常用的设计模式。它允许根据反序列化类型选择实现,而不必关心数据格式细节。

使用示例

反序列化常见类型

biome_deserialize 为常见的 Rust 数据结构实现了 Deserializable

以下示例中,我们将反序列化一个布尔值、一个整数数组和一个字符串-整数对的未排序映射。

use biome_deserialize::json::deserialize_from_json_str;
use biome_deserialize::Deserialized;
use biome_json_parser::JsonParserOptions;

let json = "false";
let Deserialized {
    deserialized,
    diagnostics,
} = deserialize_from_json_str::<bool>(&source, JsonParserOptions::default());
assert_eq!(deserialized, Some(false));
assert!(diagnostics.is_empty());

let json = "[0, 1]";
let Deserialized {
    deserialized,
    diagnostics,
} = deserialize_from_json_str::<Vec<u8>>(&source, JsonParserOptions::default());
assert_eq!(deserialized, Some(vec![0, 1]));
assert!(diagnostics.is_empty());

use std::collections::HashMap;
let json = r#"{ "a": 0, "b": 1 }"#;
let Deserialized {
    deserialized,
    diagnostics,
} = deserialize_from_json_str::<HashMap<String, u8>>(&source, JsonParserOptions::default());
assert_eq!(deserialized, Some(HashMap::from([("a".to_string(), 0), ("b".to_string(), 1)])));
assert!(diagnostics.is_empty());

可序列化 derive 宏

对于更复杂的数据类型,如结构体和枚举,biome_deserialize_macros 提供了一个 derive 宏,它可以为你生成 Deserializable 特性的实现。

例如

#[derive(Clone, Debug, Default, Deserializable)]
pub struct MyRuleOptions {
    behavior: Behavior,
    threshold: u8,
    behavior_exceptions: Vec<String>
}

如果你在 IDE 中将鼠标悬停在宏上,也可以获得该宏的详细文档。

请注意,该宏有两个主要限制

  • 当前实现仅支持没有自定义字段的枚举、具有命名字段的结构体和新类型。
  • 每个字段也必须实现 Deserializable

如果你想要为新的原始类型或上述限制之外的复杂类型实现 Deserializable,你需要手动实现它。有关 实现自定义反序列化器 的部分请参阅下文。

反序列化映射和数组集合

biome_deserialize 要求每个数据格式都支持数组和映射。在 Rust 中,映射和数组可以反序列化为多种类型。

biome_deserialize 可以将数组反序列化为 VecHashSetIndexSet

  • Vec 保留插入顺序并允许值重复;
  • HashSet 不保留插入顺序 并不允许值重复;
  • IndexSet 保留插入顺序并不允许值重复。

biome_deserialize 可以将映射反序列化为 HashMapBTreeMapIndexMap

  • HashMapBTreeMap 不保留插入顺序
  • IndexMap 保留插入顺序。

如果你在保留插入顺序的集合和不保留插入顺序的集合之间犹豫不决,请选择保留插入顺序的集合。这通常会产生更少令人惊讶的行为。

实现自定义反序列化器

对于大多数枚举和结构体,可以使用 Deserializable 宏生成实现。然而,对于原始类型和某些复杂类型,你可能需要自己实现 biome_deserialize::Deserializable

[!NOTE] 如果你对手动宏生成的代码感兴趣,Rust Analyzer 提供了一个很好的功能,可以从命令面板中调用 "在光标处递归展开宏"。只需将光标放在 Deserializable 上的 #[derive(...)] 语句上,并调用该命令。应该会打开一个面板,显示展开的宏代码。

我们下面提供了一些自定义实现的示例。

自定义整数范围

有时你需要反序列化一个整数并确保它在两个给定整数之间。

例如,假设我们想要反序列化一个由1到365之间的整数表示的一天。我们可以使用Rust中的新类型习语

use std::str::FromStr;
use biome_deserialize::{Deserializable, DeserializableValue, DeserializationDiagnostic, TextNumber};

#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Ord, PartialOrd)]
pub struct Day(u16);

impl Day {
    pub const MIN: Day = Day(1);
    pub const MAX: Day = Day(365);
    pub fn get(&self) -> u16 {
        self.0
    }
}

impl Default for Day {
    fn default() -> Self {
        Self::MIN
    }
}

impl TryFrom<u16> for Day {
    type Error = &'static str;
    fn try_from(value: u16) -> Result<Self, Self::Error> {
        if (Self::MIN.get()..=Self::MAX.get()).contains(&value) {
            Ok(Self(value))
        } else {
            Err("A day must be between 1 and 365")
        }
    }
}

impl FromStr for Day {
    type Err = &'static str;
    fn from_str(s: &str) -> Result<Self, Self::Err> {
        s.parse::<u16>()
            .map_err(|_error| "A day must be an integer between 1 and 365")
            .and_then(|value| Day::try_from(value))
    }
}

impl Deserializable for Day {
    fn deserialize(
        value: &impl DeserializableValue,
        name: &str,
        diagnostics: &mut Vec<DeserializationDiagnostic>,
    ) -> Option<Self> {
        // We deserialize the value into a number represented as a string.
        let value_text = TextNumber::deserialize(value, name, diagnostics)?;
        // We attempt to convert the string into a `Day`.
        value_text.parse::<Day>().map_err(|error| {
            // If the conversion failed, then we report the error.
            diagnostics.push(DeserializationDiagnostic::new(error).with_range(value.range()));
        }).ok()
    }
}

use biome_deserialize::json::deserialize_from_json_str;
use biome_deserialize::Deserialized;
use biome_json_parser::JsonParserOptions;

let json = "42";
let Deserialized {
    deserialized,
    diagnostics,
} = deserialize_from_json_str::<Day>(&source, JsonParserOptions::default());
assert_eq!(deserialized, Some(Day(42)));
assert!(diagnostics.is_empty());

let json = "999";
let Deserialized {
    deserialized,
    diagnostics,
} = deserialize_from_json_str::<Day>(&source, JsonParserOptions::default());
assert_eq!(deserialized, None);
assert_eq!(diagnostics..len(), 1);

反序列化联合体

有时我们希望允许单个值有几种类型。例如,假设我们想要接受一个JSON值,该值可以是字符串或布尔值。

在这种情况下,我们需要检查DeserializableValue的类型,以确定使用哪个反序列化器。

use biome_deserialize::{DeserializationDiagnostic, Deserializable, DeserializableValue, DeserializationVisitor, Text, VisitableType};
use biome_rowan::TextRange;

#[derive(Debug, Eq, PartialEq)]
enum Union {
    Bool(bool),
    Str(String),
}

impl Deserializable for Union {
    fn deserialize(
        value: &impl DeserializableValue,
        name: &str,
        diagnostics: &mut Vec<DeserializationDiagnostic>,
    ) -> Option<Self> {
        if value.is_type(VisitableType::BOOL) {
            biome_deserialize::Deserializable::deserialize(value, rule_name, diagnostics)
                .map(Self::Bool)
        } else {
            biome_deserialize::Deserializable::deserialize(value, rule_name, diagnostics)
                .map(Self::Str)
        }
    }
}

use biome_deserialize::json::deserialize_from_json_str;
use biome_json_parser::JsonParserOptions;

let source = r#" "string" "#;
let deserialized = deserialize_from_json_str::<Union>(&source, JsonParserOptions::default());
assert!(!deserialized.has_errors());
assert_eq!(deserialized.into_deserialized(), Some(Union::Str("string".to_string())));

let source = "true";
let deserialized = deserialize_from_json_str::<Union>(&source, JsonParserOptions::default());
assert!(!deserialized.has_errors());
assert_eq!(deserialized.into_deserialized(), Some(Union::Bool(true)));

或者,我们可以使用自定义访问者来实现上述功能。请注意,这种方法更为复杂,通常作为最后的选择。在这里,我们将为了演示目的重新实现Deserializable以适用于我们的Union类型,并使用访问者。

要创建自己的访问者,从以你的类型命名的结构体开始,并以Visitor后缀命名。我们将命名为UnionVisitor

访问者结构体不需要任何字段,但我们确实需要在该结构体上实现DeserializationVisitor。一个DeserializationVisitor提供了几个visit_方法,你必须实现你期望的类型(s)的visit_方法。在这里,我们期望一个布尔值或一个字符串,所以我们将实现visit_boolvisit_str

我们还需要将关联类型Output设置为期望的类型联合:VisitableType::BOOL::union::VisitableType::STR

完整的示例

impl Deserializable for Union {
    fn deserialize(
        value: &impl DeserializableValue,
        name: &str,
        diagnostics: &mut Vec<DeserializationDiagnostic>,
    ) -> Option<Self> {
        // Delegate deserialization to `UnionVisitor`
        value.deserialize(UnionVisitor, name, diagnostics)
    }
}

struct UnionVisitor;
impl DeserializationVisitor for UnionVisitor {
    type Output = Union;

    // We expect a `bool` or a `str` as data type.
    const EXPECTED_TYPE: VisitableType = VisitableType::BOOL.union(VisitableType::STR);

    // Because we expect a `bool` or a `str`, we have to implement the associated method `visit_bool`.
    fn visit_bool(
        self,
        value: bool,
        range: TextRange,
        _name: &str,
        diagnostics: &mut Vec<DeserializationDiagnostic>,
    ) -> Option<Self::Output> {
        Some(Union::Bool(value))
    }

    // Because we expect a `bool` or a `str`, we have to implement the associated method `visit_str`.
    fn visit_str(
        self,
        value: Text,
        range: TextRange,
        _name: &str,
        diagnostics: &mut Vec<DeserializationDiagnostic>,
    ) -> Option<Self::Output> {
        Some(Union::Str(value.text().to_string()))
    }
}

为值枚举实现Deserializable

假设我们想要反序列化一个JSON字符串,该字符串可以是AB。我们通过Rust的enum来表示这个字符串。

为了为Variant实现Deserializable,我们可以重用String,因为biome_deserializeString类型实现了Deserializable

我们的实现尝试反序列化一个字符串并创建相应的变体。如果变体未知,我们报告一个诊断。

use biome_deserialize::{Deserializable, DeserializableValue, DeserializationDiagnostic};

#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Ord, PartialOrd)]
pub enum Variant { A, B }

impl Deserializable for Variant {
    fn deserialize(
        value: &impl DeserializableValue,
        name: &str,
        diagnostics: &mut Vec<DeserializationDiagnostic>,
    ) -> Option<Self> {
        match String::deserialize(value, name, diagnostics)? {
            "A" => Some(Variant::A),
            "B" => Some(Variant::B),
            unknown_variant => {
                const ALLOWED_VARIANTS: &[&str] = &["A", "B"];
                diagnostics.push(DeserializationDiagnostic::new_unknown_value(
                    unknown_variant,
                    value.range(),
                    ALLOWED_VARIANTS,
                ));
                None
            }
        }
    }
}

use biome_deserialize::json::deserialize_from_json_str;
use biome_deserialize::Deserialized;
use biome_json_parser::JsonParserOptions;

let json = "\"A\"";
let Deserialized {
    deserialized,
    diagnostics,
} = deserialize_from_json_str::<Day>(&source, JsonParserOptions::default());
assert_eq!(deserialized, Some(Variant::A));
assert!(diagnostics.is_empty());

我们可以通过使用Text而不是String来避免字符串的堆分配。内部上,Text借用源的一个切片。

use biome_deserialize::{Deserializable, DeserializableValue, DeserializationDiagnostic, Text};

#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Ord, PartialOrd)]
pub enum Variant { A, B }

impl Deserializable for Variant {
    fn deserialize(
        value: &impl DeserializableValue,
        name: &str,
        diagnostics: &mut Vec<DeserializationDiagnostic>,
    ) -> Option<Self> {
        match Text::deserialize(value, name, diagnostics)?.text() {
            "A" => Some(Variant::A),
            "B" => Some(Variant::B),
            unknown_variant => {
                const ALLOWED_VARIANTS: &[&str] = &["A", "B"];
                diagnostics.push(DeserializationDiagnostic::new_unknown_value(
                    unknown_variant,
                    value.range(),
                    ALLOWED_VARIANTS,
                ));
                None
            }
        }
    }
}

为结构体实现Deserializable

假设我们想要将一个JSON映射(对象)反序列化为Rust struct的实例。

由于结构体有自定义字段和类型,我们不能重用现有的可序列化类型。我们必须将反序列化委托给访问者。

为了实现这一点,我们创建了一个零大小的结构体 PersonViistor,它实现了 DeserializationVisitor。一个 DeserializationVisitor 提供了几个 visit_ 方法。您必须实现您期望的类型 visit_ 方法。这里我们期望一个键值对映射。因此,我们实现了 visit_map 并将关联的常量 EXPECTED_TYPE 设置为 VisitableType::MAP。我们还设置了关联的类型 Output 为我们想要生成的类型:Person

PersonDeserialziable 实现简单地将反序列化操作委托给访问者。内部,value 的反序列化会调用与其类型相对应的 visit_ 方法。如果值是一个键值对映射,则调用 visit_map。否则,调用另一个 visit_ 方法。默认的 visit_ 方法会报告不正确的类型诊断。

我们的 visit_map 实现遍历键值对映射。它尝试将每个键反序列化为字符串,并根据键的内容反序列化值。如果键是 name,则反序列化为 String。如果键是 age,则反序列化为 u8。由于 Stringu8 实现了 Deserializable,因此我们可以重用 String::deserializeu8::deserialize

请注意,如果您同时使用 Serdebiome_deserialize,您必须消除对 deserialize 的调用歧义。因此,您应该使用 Deserialize::deserialize 而不是使用 String::deserializeu8::deserialize

use biome_deserialize::{DeserializationDiagnostic, Deserializable, DeserializableValue, DeserializationVisitor, Text, VisitableType};
use biome_rowan::TextRange;

#[derive(Debug, Default, Eq, PartialEq, Clone)]
pub struct Person { name: String, age: u8 }

impl Deserializable for Person {
    fn deserialize(
        value: &impl DeserializableValue,
        name: &str,
        diagnostics: &mut Vec<DeserializationDiagnostic>,
    ) -> Option<Self> {
        // Delegate the deserialization to `PersonVisitor`.
        // `value` will call the `PersonVisitor::viist_` method that corresponds to its type.
        value.deserialize(PersonVisitor, name, diagnostics)
    }
}

struct PersonVisitor;
impl DeserializationVisitor for PersonVisitor {
    // The visitor deserialize a [Person].
    type Output = Person;

    // We expect a `map` as data type.
    const EXPECTED_TYPE: VisitableType = VisitableType::MAP;

    // Because we expect a `map`, we have to implement the associated method `visit_map`.
    fn visit_map(
        self,
        // Iterator of key-value pairs.
        members: impl Iterator<Item = Option<(impl DeserializableValue, impl DeserializableValue)>>,
        // range of the map in the source text.
        range: TextRange,
        _name: &str,
        diagnostics: &mut Vec<DeserializationDiagnostic>,
    ) -> Option<Self::Output> {
        let mut result = Person::default();
        for (key, value) in members.flatten() {
            // Try to deserialize the key as a string.
            // We use `Text` to avoid an heap-allocation.
            let Some(key_text) = Text::deserialize(&key, "", diagnostics) else {
                // If this failed, then pass to the next key-value pair.
                continue;
            };
            match key_text.text() {
                "name" => {
                    if let Some(name) = String::deserialize(&value, &key_text, diagnostics) {
                        result.name = name;
                    }
                },
                "age" => {
                    if let Some(age) = u8::deserialize(&value, &key_text, diagnostics) {
                        result.age = age;
                    }
                },
                unknown_key => {
                    const ALLOWED_KEYS: &[&str] = &["name"];
                    diagnostics.push(DeserializationDiagnostic::new_unknown_key(
                        unknown_key,
                        key.range(),
                        ALLOWED_KEYS,
                    ));
                }
            }
        }
        Some(result)
    }
}

use biome_deserialize::json::deserialize_from_json_str;
use biome_json_parser::JsonParserOptions;

let source = r#"{ "name": "Isaac Asimov" }"#;
let deserialized = deserialize_from_json_str::<Person>(&source, JsonParserOptions::default());
assert!(!deserialized.has_errors());
assert_eq!(deserialized.into_deserialized(), Some(Person { name: "Isaac Asimov".to_string() }));

依赖项

~9–19MB
~241K SLoC