serde-flattened 0.1.1

A `csv` and `serde_json` extension for flattening nested structures into flat representations. This enables for example serialization/deserialization of nested data to/from CSV.
Documentation
use {
    super::{FieldPath, JOIN_TAG, Segment, boxed_iter},
    itertools::Itertools,
    serde_json::Value,
    std::{borrow::Cow, iter::once},
    tap::Pipe,
};

pub fn flattened_iter<'prefix>(prefix: FieldPath<'prefix>, value: Value) -> impl Iterator<Item = (FieldPath<'static>, Value)> {
    match value {
        Value::Array(arr) => arr
            .into_iter()
            .enumerate()
            .flat_map({
                let prefix = prefix.clone();
                move |(idx, value)| flattened_iter(prefix.clone().join(Segment::Idx(idx)), value)
            })
            .pipe(boxed_iter),
        Value::Object(map) => map
            .into_iter()
            .flat_map({
                let prefix = prefix.clone();
                move |(key, value)| flattened_iter(prefix.clone().join(Segment::Field(Cow::Owned(key))), value)
            })
            .pipe(boxed_iter),
        other => once((prefix.to_owned(), other)).pipe(boxed_iter),
    }
    .pipe(boxed_iter)
}

pub fn flattened(value: serde_json::Value) -> serde_json::Map<String, serde_json::Value> {
    flattened_iter(Default::default(), value)
        .map(|(k, v)| (k.0.iter().map(|k| k.to_string()).join(JOIN_TAG), v))
        .collect()
}

pub fn assert_flattened(value: serde_json::Value) -> Result<serde_json::Map<String, serde_json::Value>, serde_json::Value> {
    match value {
        Value::Object(map) => Ok(map),
        other => Err(other),
    }
}

#[cfg(test)]
mod tests {
    use {super::*, serde_json::json, tap::Tap};

    #[test]
    fn test_flatten_simple() {
        let input = json!({
            "name": "John",
            "age": 30
        });

        let result = flattened(input);
        assert_eq!(result.get("name").unwrap(), &json!("John"));
        assert_eq!(result.get("age").unwrap(), &json!(30));
    }

    #[test]
    fn test_flatten_nested() {
        let input = json!({
            "user": {
                "name": "John",
                "address": {
                    "city": "NYC",
                    "zip": "10001"
                }
            },
            "active": true
        });

        let result = flattened(input);
        assert_eq!(
            (&result)
                .tap(|r| println!("{r:#?}"))
                .get(&format!("user{JOIN_TAG}name"))
                .unwrap(),
            &json!("John")
        );
        assert_eq!(
            (&result)
                .tap(|r| println!("{r:#?}"))
                .get(&format!("user{JOIN_TAG}address{JOIN_TAG}city"))
                .unwrap(),
            &json!("NYC")
        );
        assert_eq!(
            (&result)
                .tap(|r| println!("{r:#?}"))
                .get(&format!("user{JOIN_TAG}address{JOIN_TAG}zip"))
                .unwrap(),
            &json!("10001")
        );
        assert_eq!(result.get("active").unwrap(), &json!(true));
    }
}