Skip to content

Instantly share code, notes, and snippets.

@aeshirey
Last active February 21, 2023 19:32
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save aeshirey/11baea190778e36754be51f348c8d062 to your computer and use it in GitHub Desktop.
Save aeshirey/11baea190778e36754be51f348c8d062 to your computer and use it in GitHub Desktop.
Rust binary to remove `nulls` from JavaScript input
//! To use this gist:
//!
//! ```shell
//! cargo new remove-nulls
//! cd remove-nulls
//! cargo add serde serde_json
//! ```
//!
//! Then paste the contents of this file into src/main.rs.
//! Run `cargo build --release`
//!
//! You can pipe data through it:
//!
//! ```shell
//! $ cat my_file.json | ./remove-nulls > my_file_no_nulls.json
//! $ echo '{"foo":"bar","nothing":null}' | ./remove-nulls
//! {"foo":"bar"}
//!
//! Or you can use the --in=X --out=Y flags:
//!
//! ./remove-nulls --in=my_file.json --out=my_file_no_nulls.json
//!
//! By default, empty collections are kept. For example, {"foo":"bar", "nothing":[null]}
//! will become {"foo":"bar","nothing":[]}. But if you want to remove this empty array,
//! pass the `--remove-empty-collections` or `-c` flag when running
//!
//! ```shell
//! $ echo '{"foo":"bar", "nothing":[null]}' | ./remove-nulls
//! {"foo":"bar","nothing":[]}
//! $ echo '{"foo":"bar", "nothing":[null]}' | ./remove-nulls -c
//! {"foo":"bar"}
use serde_json::{Map, Value};
use std::fs;
use std::io::{self, BufRead, BufReader, Write};
fn main() {
let mut input = None;
let mut output = None;
let mut remove_empty_collections = false;
for arg in std::env::args().skip(1) {
if let Some(inarg) = arg.strip_prefix("--in=") {
input = Some(inarg.to_string());
} else if let Some(outarg) = arg.strip_prefix("--out=") {
output = Some(outarg.to_string());
} else if arg == "--remove-empty-collections" || arg == "-c" {
remove_empty_collections = true;
}
}
let instream: Box<dyn BufRead> = match input {
Some(i) => {
let fh = fs::File::open(i).unwrap();
Box::new(BufReader::new(fh))
}
None => Box::new(io::stdin().lock()),
};
let mut outstream: Box<dyn Write> = match output {
Some(o) => {
let fh = fs::File::create(o).unwrap();
Box::new(io::BufWriter::new(fh))
}
None => Box::new(io::stdout()),
};
for line in instream.lines().flatten() {
let Ok(parsed) = serde_json::from_str::<Value>(&line) else { continue };
if let Some(value) = remove_nulls(parsed, remove_empty_collections) {
let serialized = serde_json::to_string(&value).unwrap();
writeln!(outstream, "{serialized}").unwrap();
}
}
}
fn remove_nulls(value: Value, remove_empty_collections: bool) -> Option<Value> {
match value {
Value::Null => None,
Value::Bool(_) => Some(value),
Value::Number(_) => Some(value),
Value::String(_) => Some(value),
Value::Array(values) => {
let removed = values
.into_iter()
.filter_map(|v| remove_nulls(v, remove_empty_collections))
.collect::<Vec<_>>();
if remove_empty_collections && removed.is_empty() {
None
} else {
Some(Value::Array(removed))
}
}
Value::Object(map) => {
let removed: Map<_, _> = map
.into_iter()
.filter_map(|(k, v)| remove_nulls(v, remove_empty_collections).map(|v| (k, v)))
.collect();
if remove_empty_collections && removed.is_empty() {
None
} else {
Some(Value::Object(removed))
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment