Rust parsing dynamic JSON
JSON is probably the most commonly used data structure. You can use it for configurations, databases, etc.
The best practice for json data structure is to have static key for each attribute. But sometime we need to deal with dynamic key.
serde_json
In rust we are using serde_json library it has nice api to work with. So let's take a look how to parse json with serde_json.
Let's add serde dependency to Cargo.toml. We will use serde to serialize our data to struct.
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
Now let's parse some json with static key properties.
use serde::{Deserialize, Serialize};
use serde_json::Result;
#[derive(Serialize, Deserialize)]
struct User {
username: String,
age: usize,
email: String,
full_name: Vec<String>,
}
pub fn main() -> Result<()> {
let data = r#"
{
"username": "ahmadrosid",
"email": "alahmadrosid@gmail.com",
"age": 27,
"full_name": [
"Ahmad",
"Rosid"
]
}"#;
let u: User = serde_json::from_str(data)?;
println!("Hi {}", u.full_name[0]);
Ok(())
}
With static key property we can easily just declare the key and type to the struct. But if the key is dynamic we can not handle that.
Dynamic Json
Luckily serde_json have this enum to work with json data structure.
enum Value {
Null,
Bool(bool),
Number(Number),
String(String),
Array(Vec<Value>),
Object(Map<String, Value>),
}
With this enum we can check every key and do some serialization on that. Now, suppose we have this json.
{
"plugins": {
"width": [["w",["width"]]],
"height": [["h",["height"]]],
"z_index": [["z",["z-index"]]],
}
}
It seems simple right? We can easily declare the struct like this.
#[derive(Serialize, Deserialize)]
struct Data {
plugins: Plugin,
}
#[derive(Serialize, Deserialize)]
struct Plugin {
width: Vec<Value>,
height: Vec<Value>,
z_index: Vec<Value>,
}
But what if the key inside plugins
is dynamic for example what if the z_index
become z-index
and the keys is more that those three in the example.
So to handle that case we need to turn this data to Map<String, Value>
. Now we can write our struct to become like this.
#[derive(Serialize, Deserialize)]
struct Data {
plugins: Map<String, Value>,
}
Now we can access the data like this.
let data: Data = serde_json::from_str(json)?;
let z_index: Value = data.get("z-index").unwrap();
To turn the Value
to array we can do it like this.
let z_index_arr: Vec<&Value> = z_index.as_array().unwrap();
We also can improve by transforming the value inside z_index which is an array to key value. As you can see the value of z_index
is array inside array.
[
[
"z",
[
"z-index"
]
]
]
Let's turn this value to:
{
"z": ["z-index"]
}
To do that we will work with Map<String, Value> directly.
let z_index: Vec<&Value> = data.get("z-index").unwrap().as_array().unwrap();
let mut data = Map::new();
for item in z_index {
if item.get(0)?.is_string() {
let key = item.get(0)?.as_str()?.to_string();
let variants = item.get(1)?.clone();
}
}
Pro tip! If you don't want to use
unwrap()
every time you convert enumValue
just use ? and wrap your function with Option<()>
Now we can access the value of z-index
like this.
let arr: Vec<&Value> = data.get("z")?;
I think that's all, more documentation for serde here.