bms_table/
de.rs

1//! 反序列化实现模块
2//!
3//! 将所有 `Deserialize` 实现与辅助原始类型集中于此,保持 `lib.rs` 仅包含类型定义。
4#![cfg(feature = "serde")]
5
6use serde::{Deserialize, Deserializer};
7use serde_json::Value;
8use std::collections::BTreeMap;
9
10use crate::{ChartItem, CourseInfo, Trophy};
11
12/// 字段级反序列化:支持 `course` 为 `Vec<CourseInfo>` 或 `Vec<Vec<CourseInfo>>`,
13/// 并在空数组时返回 `vec![Vec::new()]`,保持旧行为。
14pub(crate) fn deserialize_course_groups<'de, D>(
15    deserializer: D,
16) -> Result<Vec<Vec<CourseInfo>>, D::Error>
17where
18    D: Deserializer<'de>,
19{
20    let Some(Value::Array(arr)) = Option::<Value>::deserialize(deserializer)? else {
21        return Ok(Vec::new());
22    };
23    if arr.is_empty() {
24        return Ok(vec![Vec::new()]);
25    }
26
27    if matches!(arr.first(), Some(Value::Array(_))) {
28        serde_json::from_value::<Vec<Vec<CourseInfo>>>(Value::Array(arr))
29            .map_err(serde::de::Error::custom)
30    } else {
31        let inner: Vec<CourseInfo> =
32            serde_json::from_value(Value::Array(arr)).map_err(serde::de::Error::custom)?;
33        Ok(vec![inner])
34    }
35}
36
37/// 字段级反序列化:将 `level_order` 的数字或字符串转换为字符串,
38/// 其他类型使用 `to_string()`,缺省时返回空数组。
39pub(crate) fn deserialize_level_order<'de, D>(deserializer: D) -> Result<Vec<String>, D::Error>
40where
41    D: Deserializer<'de>,
42{
43    let values = Option::<Vec<Value>>::deserialize(deserializer)?.unwrap_or_default();
44    Ok(values
45        .into_iter()
46        .map(|v| match v {
47            Value::Number(n) => n.to_string(),
48            Value::String(s) => s,
49            other => other.to_string(),
50        })
51        .collect())
52}
53
54/// 内部辅助类型:用于更简洁地构造 `CourseInfo`,并处理 md5/sha256 列表。
55#[derive(Deserialize)]
56struct CourseInfoRaw {
57    name: String,
58    #[serde(default)]
59    constraint: Vec<String>,
60    #[serde(default)]
61    trophy: Vec<Trophy>,
62    #[serde(default, rename = "md5")]
63    md5list: Vec<String>,
64    #[serde(default, rename = "sha256")]
65    sha256list: Vec<String>,
66    #[serde(default)]
67    charts: Vec<Value>,
68}
69
70impl TryFrom<CourseInfoRaw> for CourseInfo {
71    type Error = String;
72
73    fn try_from(raw: CourseInfoRaw) -> Result<Self, Self::Error> {
74        let mut charts: Vec<ChartItem> =
75            Vec::with_capacity(raw.charts.len() + raw.md5list.len() + raw.sha256list.len());
76
77        // 处理 charts,缺失 level 时补 "0"
78        for mut chart_value in raw.charts {
79            if chart_value.get("level").is_none() {
80                let obj = chart_value
81                    .as_object()
82                    .ok_or_else(|| "chart_value is not an object".to_string())?
83                    .clone();
84                let mut obj = obj;
85                obj.insert("level".to_string(), Value::String("0".to_string()));
86                chart_value = Value::Object(obj);
87            }
88            let item: ChartItem = serde_json::from_value(chart_value).map_err(|e| e.to_string())?;
89            charts.push(item);
90        }
91
92        // md5list -> charts
93        charts.extend(raw.md5list.into_iter().map(|md5| ChartItem {
94            level: "0".to_string(),
95            md5: Some(md5),
96            sha256: None,
97            title: None,
98            subtitle: None,
99            artist: None,
100            subartist: None,
101            url: None,
102            url_diff: None,
103            extra: BTreeMap::new(),
104        }));
105
106        // sha256list -> charts
107        charts.extend(raw.sha256list.into_iter().map(|sha256| ChartItem {
108            level: "0".to_string(),
109            md5: None,
110            sha256: Some(sha256),
111            title: None,
112            subtitle: None,
113            artist: None,
114            subartist: None,
115            url: None,
116            url_diff: None,
117            extra: BTreeMap::new(),
118        }));
119
120        Ok(Self {
121            name: raw.name,
122            constraint: raw.constraint,
123            trophy: raw.trophy,
124            charts,
125        })
126    }
127}
128
129impl<'de> Deserialize<'de> for CourseInfo {
130    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
131    where
132        D: Deserializer<'de>,
133    {
134        let raw = CourseInfoRaw::deserialize(deserializer)?;
135        Self::try_from(raw).map_err(serde::de::Error::custom)
136    }
137}
138
139/// 将空字符串反序列化为 `None` 的通用辅助函数。
140pub(crate) fn de_numstring<'de, D>(deserializer: D) -> Result<String, D::Error>
141where
142    D: Deserializer<'de>,
143{
144    let opt = Option::<Value>::deserialize(deserializer)?;
145    let Some(value) = opt else {
146        return Err(serde::de::Error::custom(
147            "expected string or number, found None",
148        ));
149    };
150    match value {
151        Value::String(s) => Ok(s),
152        Value::Number(n) => Ok(n.to_string()),
153        other => Err(serde::de::Error::custom(format!(
154            "expected string or number, got {}",
155            other
156        ))),
157    }
158}