Last active
February 21, 2023 19:32
-
-
Save aeshirey/11baea190778e36754be51f348c8d062 to your computer and use it in GitHub Desktop.
Rust binary to remove `nulls` from JavaScript input
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//! 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