├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── readme.md ├── src ├── lib.rs └── main.rs └── tests ├── simple.json ├── simple.min.json ├── types.json ├── types.min.json └── weird.json /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /tests/vsat.json -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | [[package]] 4 | name = "pick-json" 5 | version = "0.1.0" 6 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "pick-json" 3 | version = "0.1.0" 4 | authors = ["Gerard Rodes "] 5 | edition = "2018" 6 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # pick json 2 | Read a single attribute of a json and stop there 3 | 4 | ## how does it work? 5 | instead of reading the whole json it stops reading bytes once found the picked attribute and its value is returned 6 | 7 | ## run it 8 | ```shell 9 | $ cargo build --release 10 | $ target/release/pick-json tests/types.json emoji 11 | "🔥" 12 | ``` 13 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | use std::fs::File; 2 | use std::io::{Error, ErrorKind, BufRead, BufReader}; 3 | 4 | pub fn pick_json (filepath: &str, property: &str) -> Result { 5 | let f = File::open(filepath)?; 6 | let mut f = BufReader::new(f); 7 | 8 | let mut is_prop = true; 9 | let mut found = false; 10 | let mut escaped = false; 11 | let mut is_reading_text = false; 12 | 13 | let mut buffer = vec![]; 14 | let mut data: Vec = vec![]; 15 | while f.read_until(b',', &mut buffer)? > 0 { 16 | // println!("BUFFER: {:?}", buffer.iter().map(|b| *b as char).collect::>()); 17 | 18 | let mut i = 0; 19 | 'outer: while i < buffer.len() { 20 | let non_data_byte = buffer[i]; 21 | 22 | if !is_reading_text { 23 | if non_data_byte == b':' { 24 | is_prop = false; 25 | i += 1; 26 | continue; 27 | } else if non_data_byte == b',' { 28 | is_prop = true; 29 | i += 1; 30 | continue; 31 | } else if 32 | non_data_byte == b'\n' || 33 | non_data_byte == b'{' || 34 | non_data_byte == b'}' || 35 | non_data_byte == b'[' || 36 | non_data_byte == b']' || 37 | non_data_byte == b' ' || 38 | non_data_byte == b'\t' || 39 | non_data_byte == b'\r' { 40 | i += 1; 41 | continue; 42 | } 43 | } 44 | 45 | while i < buffer.len() { 46 | let byte = buffer[i]; 47 | 48 | // println!("char[{}](rt: {} | prop: {} | found: {}): {:?} => {:?}", i, is_reading_text, is_prop, found, byte as char, data.iter().map(|b| *b as char).collect::>()); 49 | 50 | if escaped { 51 | // println!("\tscapped"); 52 | data.push(byte); 53 | escaped = false; 54 | i += 1; 55 | continue 56 | } 57 | 58 | match byte { 59 | b'\\' => escaped = true, 60 | b':' | b',' | b'\n' | b'}' | b']' | b' ' | b'\t' | b'\r' if !is_reading_text => { 61 | let data_value = String::from_utf8_lossy(&data); 62 | 63 | if found { 64 | // println!("FOUND PICKED VALUE: {}", data_value); 65 | return Ok(data_value.to_string()); 66 | } 67 | 68 | data.clear(); 69 | is_prop = true; 70 | i += 1; 71 | continue 'outer 72 | }, 73 | b'"' if is_reading_text => { 74 | if is_prop { 75 | // remove quotation 76 | data.remove(0); 77 | } else { 78 | data.push(byte); 79 | } 80 | 81 | let data_value = String::from_utf8_lossy(&data); 82 | 83 | if found { 84 | // println!("FOUND PICKED VALUE: {}", data_value); 85 | return Ok(data_value.to_string()); 86 | } 87 | 88 | found = found || (is_prop && (data_value == property)); 89 | 90 | data.clear(); 91 | i += 1; 92 | is_reading_text = false; 93 | continue 'outer 94 | }, 95 | _ => { 96 | data.push(byte); 97 | is_reading_text = is_reading_text || byte == b'"'; 98 | }, 99 | } 100 | 101 | i += 1; 102 | } 103 | 104 | i += 1; 105 | } 106 | 107 | buffer.clear() 108 | } 109 | 110 | 111 | Err(Error::from(ErrorKind::NotFound)) 112 | } 113 | 114 | #[cfg(test)] 115 | mod tests { 116 | use super::*; 117 | 118 | #[test] 119 | fn simple() { 120 | assert_eq!(pick_json("tests/simple.json", "text").unwrap(), String::from("\"hi\"")); 121 | } 122 | 123 | #[test] 124 | fn simple_min() { 125 | assert_eq!(pick_json("tests/simple.min.json", "text").unwrap(), String::from("\"hi\"")); 126 | } 127 | 128 | #[test] 129 | fn types_text() { 130 | assert_eq!(pick_json("tests/types.json", "text").unwrap(), String::from("\"hi\"")); 131 | } 132 | 133 | #[test] 134 | fn types_number() { 135 | assert_eq!(pick_json("tests/types.json", "number").unwrap(), String::from("1")); 136 | } 137 | 138 | #[test] 139 | fn types_boolean() { 140 | assert_eq!(pick_json("tests/types.json", "boolean").unwrap(), String::from("true")); 141 | } 142 | 143 | #[test] 144 | fn types_emoji() { 145 | assert_eq!(pick_json("tests/types.json", "emoji").unwrap(), String::from("\"🔥\"")); 146 | } 147 | 148 | #[test] 149 | fn types_min() { 150 | assert_eq!(pick_json("tests/types.min.json", "text").unwrap(), String::from("\"hi\"")); 151 | assert_eq!(pick_json("tests/types.min.json", "number").unwrap(), String::from("1")); 152 | assert_eq!(pick_json("tests/types.min.json", "boolean").unwrap(), String::from("true")); 153 | assert_eq!(pick_json("tests/types.min.json", "emoji").unwrap(), String::from("\"🔥\"")); 154 | } 155 | 156 | #[test] 157 | fn weird_escaped_n() { 158 | assert_eq!(pick_json("tests/weird.json", "escaped_n").unwrap(), String::from("\"hi\\n\"")); 159 | } 160 | 161 | #[test] 162 | fn weird_escaped_n_and_r() { 163 | assert_eq!(pick_json("tests/weird.json", "escaped_n_and_r").unwrap(), String::from("\"hi\\n\\\\r\"")); 164 | } 165 | 166 | #[test] 167 | fn weird_escaped_n_and_r_and_keys() { 168 | assert_eq!(pick_json("tests/weird.json", "escaped_n_and_r_and_keys").unwrap(), String::from("\"hi\\n\\\\r,:\"")); 169 | } 170 | 171 | #[test] 172 | fn weird_number() { 173 | assert_eq!(pick_json("tests/weird.json", "number").unwrap(), String::from("1")); 174 | } 175 | 176 | #[test] 177 | fn weird_boolean() { 178 | assert_eq!(pick_json("tests/weird.json", "boolean").unwrap(), String::from("true")); 179 | } 180 | 181 | #[test] 182 | fn weird_emoji() { 183 | assert_eq!(pick_json("tests/weird.json", "emoji").unwrap(), String::from("\"🔥\"")); 184 | } 185 | } -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use std::io::Error; 2 | use std::env; 3 | mod lib; 4 | 5 | fn main () -> Result<(), Error> { 6 | let args: Vec = env::args().collect(); 7 | 8 | println!("{}", lib::pick_json(&args[1], &args[2])?); 9 | 10 | Ok(()) 11 | } -------------------------------------------------------------------------------- /tests/simple.json: -------------------------------------------------------------------------------- 1 | { 2 | "text": "hi" 3 | } -------------------------------------------------------------------------------- /tests/simple.min.json: -------------------------------------------------------------------------------- 1 | {"text": "hi"} -------------------------------------------------------------------------------- /tests/types.json: -------------------------------------------------------------------------------- 1 | { 2 | "text": "hi", 3 | "number": 1, 4 | "boolean": true, 5 | "emoji": "🔥" 6 | } -------------------------------------------------------------------------------- /tests/types.min.json: -------------------------------------------------------------------------------- 1 | {"text": "hi","number": 1,"boolean": true,"emoji": "🔥"} -------------------------------------------------------------------------------- /tests/weird.json: -------------------------------------------------------------------------------- 1 | { 2 | "escaped_n": "hi\\n", 3 | "escaped_n_and_r": "hi\\n\\\\r", 4 | "escaped_n_and_r_and_keys": "hi\\n\\\\r,:", 5 | 6 | 7 | 8 | "number": 9 | 10 | 11 | 12 | 1 , 13 | 14 | 15 | 16 | "boolean": true 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | , 25 | 26 | "emoji": "🔥" 27 | } --------------------------------------------------------------------------------