use crate::projector::{slug, CorpusKind, Projector, Situation};
use serde_json::Value;
use std::path::PathBuf;
pub struct JsonProjector {
path: PathBuf,
}
impl JsonProjector {
pub fn open(path: impl Into<PathBuf>) -> JsonProjector {
JsonProjector { path: path.into() }
}
}
fn flatten(prefix: &str, v: &Value, out: &mut Vec<String>) {
match v {
Value::Object(map) => {
for (k, child) in map {
let key = slug(k);
let path = if prefix.is_empty() { key } else { format!("{prefix}/{key}") };
flatten(&path, child, out);
}
}
Value::Array(items) => {
for item in items {
flatten(prefix, item, out);
}
}
Value::String(s) => {
let sv = slug(s);
if !sv.is_empty() && !prefix.is_empty() {
out.push(format!("{prefix}/{sv}"));
}
}
Value::Number(n) => {
if !prefix.is_empty() {
out.push(format!("{prefix}/{}", slug(&n.to_string())));
}
}
Value::Bool(b) => {
if !prefix.is_empty() {
out.push(format!("{prefix}/{b}"));
}
}
Value::Null => {}
}
}
fn situation_of(v: &Value) -> Situation {
let mut tokens = Vec::new();
flatten("", v, &mut tokens);
tokens.sort();
tokens.dedup();
Situation::new(tokens, vec![v.to_string()])
}
impl Projector for JsonProjector {
fn columns(&self) -> Vec<String> {
vec!["json".to_string()]
}
fn kind(&self) -> CorpusKind {
CorpusKind::Csv
}
fn source(&self) -> String {
self.path.display().to_string()
}
fn project(self: Box<Self>, sink: &mut dyn FnMut(Situation)) -> std::io::Result<()> {
let text = std::fs::read_to_string(&self.path)?;
match serde_json::from_str::<Value>(&text) {
Ok(Value::Array(items)) => {
for item in &items {
sink(situation_of(item));
}
}
Ok(other) => sink(situation_of(&other)),
Err(_) => {
for line in text.lines() {
let line = line.trim();
if line.is_empty() {
continue;
}
if let Ok(v) = serde_json::from_str::<Value>(line) {
sink(situation_of(&v));
}
}
}
}
Ok(())
}
}