#![deny(missing_docs)]
extern crate serde;
#[macro_use]
extern crate serde_derive;
#[cfg(test)]
#[macro_use]
extern crate serde_json;
#[cfg(not(test))]
extern crate serde_json;
use std::collections::BTreeMap;
use serde::Serialize;
use serde_json::Value;
mod scalar;
pub use scalar::Scalar;
pub use serde_json::Result;
pub fn flatten<S>(nested: S) -> Result<BTreeMap<String, Scalar>>
where
S: Serialize,
{
serde_json::to_value(nested).map(flatten_value)
}
pub fn flatten_value(value: Value) -> BTreeMap<String, Scalar> {
fn fold<'a>(
result: &'a mut BTreeMap<String, Scalar>,
val: Value,
path: Option<String>,
) -> &'a mut BTreeMap<String, Scalar> {
match val {
Value::Object(fields) => {
for (k, v) in fields.into_iter() {
fold(
result,
v,
path.clone()
.map(|p| format!("{}.{}", p, k))
.or_else(|| Some(k.to_string())),
);
}
}
Value::Array(v) => {
for (idx, elem) in v.into_iter().enumerate().into_iter() {
fold(
result,
elem,
path.clone()
.map(|p| format!("{}[{}]", p, idx))
.or_else(|| Some(format!("[{}]", idx))),
);
}
}
Value::Bool(scalar) => {
result.insert(path.unwrap_or_default(), Scalar::Bool(scalar));
}
Value::String(scalar) => {
result.insert(path.unwrap_or_default(), Scalar::String(scalar));
}
Value::Number(scalar) => {
result.insert(path.unwrap_or_default(), Scalar::Number(scalar));
}
Value::Null => {
result.insert(path.unwrap_or_default(), Scalar::Null);
}
};
result
}
fold(&mut BTreeMap::new(), value, None).to_owned()
}
#[cfg(test)]
mod tests {
use super::{flatten, serde_json, Scalar};
use std::collections::BTreeMap;
#[test]
fn flattens_nested_maps() {
let result = flatten(json!({
"foo": {
"bar": {
"baz": 3
}
}
})).unwrap();
let mut expected: BTreeMap<String, Scalar> = BTreeMap::new();
expected.insert("foo.bar.baz".into(), 3.into());
assert_eq!(result, expected)
}
#[test]
fn flattens_to_serializable() {
let result = flatten(json!({
"foo": {
"bar": {
"baz": 3
}
}
})).unwrap();
assert_eq!(
serde_json::to_string(&result).unwrap(),
r#"{"foo.bar.baz":3}"#
)
}
}