use std::collections::BTreeMap;
use serde::{Deserialize, Deserializer, Serialize};
use crate::{ReleaseElementId, ReleaseId, SeriesId};
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct ReleaseTable {
#[serde(default)]
pub name: Option<String>,
#[serde(default)]
pub element_id: Option<ReleaseElementId>,
#[serde(
rename(serialize = "roots", deserialize = "elements"),
deserialize_with = "roots_from_map"
)]
pub roots: Vec<ReleaseTableElement>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct ReleaseTableElement {
pub element_id: ReleaseElementId,
pub release_id: ReleaseId,
#[serde(default)]
pub parent_id: Option<ReleaseElementId>,
#[serde(default, deserialize_with = "optional_series_id")]
pub series_id: Option<SeriesId>,
#[serde(rename = "type")]
pub element_type: String,
pub name: String,
#[serde(default)]
pub line: Option<String>,
pub level: String,
#[serde(default, deserialize_with = "deserialize_optional_value")]
pub observation_value: Option<f64>,
#[serde(default)]
pub observation_date: Option<String>,
#[serde(default)]
pub children: Vec<ReleaseTableElement>,
}
fn roots_from_map<'de, D>(deserializer: D) -> Result<Vec<ReleaseTableElement>, D::Error>
where
D: Deserializer<'de>,
{
let map: BTreeMap<String, ReleaseTableElement> = BTreeMap::deserialize(deserializer)?;
let mut roots: Vec<ReleaseTableElement> = map.into_values().collect();
roots.sort_by_key(|element| element.element_id);
Ok(roots)
}
fn optional_series_id<'de, D>(deserializer: D) -> Result<Option<SeriesId>, D::Error>
where
D: Deserializer<'de>,
{
let raw: Option<String> = Option::deserialize(deserializer)?;
Ok(raw.filter(|id| !id.is_empty()).map(SeriesId::new))
}
fn deserialize_optional_value<'de, D>(deserializer: D) -> Result<Option<f64>, D::Error>
where
D: Deserializer<'de>,
{
let raw = String::deserialize(deserializer)?;
if raw == "." {
return Ok(None);
}
raw.parse::<f64>()
.map(Some)
.map_err(serde::de::Error::custom)
}
#[cfg(test)]
mod tests {
use super::*;
const TABLE_BODY: &str = r#"{
"name": null,
"element_id": null,
"release_id": "10",
"elements": {
"34483": {
"element_id": 34483, "release_id": 10, "parent_id": null,
"series_id": null, "type": "section", "name": "Monthly, SA",
"line": null, "level": "0",
"children": [
{
"element_id": 34484, "release_id": 10, "parent_id": 34483,
"series_id": "", "type": "series", "name": "All items",
"line": "1", "level": "1",
"children": [
{
"element_id": 34485, "release_id": 10, "parent_id": 34484,
"series_id": "CPIFABSL", "type": "series",
"name": "Food and beverages", "line": "2", "level": "2",
"children": []
}
]
}
]
}
}
}"#;
#[test]
fn deserializes_a_nested_table() {
let table: ReleaseTable = serde_json::from_str(TABLE_BODY).unwrap();
assert!(table.name.is_none());
assert!(table.element_id.is_none());
assert_eq!(table.roots.len(), 1);
let section = &table.roots[0];
assert_eq!(section.element_id, ReleaseElementId::new(34483));
assert_eq!(section.element_type, "section");
assert!(section.parent_id.is_none());
assert!(section.series_id.is_none()); assert_eq!(section.children.len(), 1);
let all_items = §ion.children[0];
assert_eq!(all_items.parent_id, Some(ReleaseElementId::new(34483)));
assert!(all_items.series_id.is_none()); assert_eq!(all_items.line.as_deref(), Some("1"));
let leaf = &all_items.children[0];
assert_eq!(leaf.series_id, Some(SeriesId::new("CPIFABSL")));
assert_eq!(leaf.element_type, "series");
assert!(leaf.children.is_empty());
}
#[test]
fn observation_values_deserialize_when_present() {
let body = r#"{
"release_id": "10",
"elements": {
"36714": {
"element_id": 36714, "release_id": 10, "type": "table",
"name": "Monthly, Seasonally Adjusted", "level": "0",
"children": [
{
"element_id": 36715, "release_id": 10, "parent_id": 36714,
"series_id": "CUSR0000SA0L5", "type": "series",
"name": "All items less medical care", "level": "1",
"observation_value": "292.260", "observation_date": "Jun 2023",
"children": []
},
{
"element_id": 36716, "release_id": 10, "parent_id": 36714,
"series_id": "CPILEGSL", "type": "series", "name": "Missing",
"level": "1",
"observation_value": ".", "observation_date": "Jun 2023",
"children": []
}
]
}
}
}"#;
let table: ReleaseTable = serde_json::from_str(body).unwrap();
let table_elem = &table.roots[0];
assert_eq!(table_elem.observation_value, None);
assert_eq!(table_elem.observation_date, None);
let with_value = &table_elem.children[0];
assert_eq!(with_value.observation_value, Some(292.260));
assert_eq!(with_value.observation_date.as_deref(), Some("Jun 2023"));
let missing = &table_elem.children[1];
assert_eq!(missing.observation_value, None);
assert_eq!(missing.observation_date.as_deref(), Some("Jun 2023"));
}
#[test]
fn observation_values_absent_when_not_requested() {
let table: ReleaseTable = serde_json::from_str(TABLE_BODY).unwrap();
let leaf = &table.roots[0].children[0].children[0];
assert_eq!(leaf.observation_value, None);
assert_eq!(leaf.observation_date, None);
}
#[test]
fn roots_are_ordered_by_element_id() {
let body = r#"{
"elements": {
"200": {"element_id":200,"release_id":1,"type":"table","name":"B","level":"0"},
"100": {"element_id":100,"release_id":1,"type":"table","name":"A","level":"0"}
}
}"#;
let table: ReleaseTable = serde_json::from_str(body).unwrap();
let ids: Vec<u32> = table.roots.iter().map(|e| e.element_id.get()).collect();
assert_eq!(ids, [100, 200]);
}
}