Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to get at one particular item in JSON file using serde_json without deriving structs?

Tags:

rust

serde

I have a complex JSON file and I'd like to extract only a single value out of it. I could define all of the structs and derive Deserialize on all of them, but I'd like to just write a little manual code to pull that one value out. The Serde documentation, quite frankly, just confused me.

My JSON content has the following layout:

{
  "data": [
    {
      "hostname": "a hostname"
    }
  ]
}

I'm looking for the value navigated to by going into data, then taking the first element of the array, and taking the value of hostname.

In Haskell, I'd do it like this:

newtype Host = Host Text

instance FromJSON Host where
    parseJSON (Object o) = (return . Host) <=< (.: "hostname") <=< (fmap (!! 0) . parseJSON) <=< (.: "data") $ o
    parseJSON _ = mzero

What's the equivalent for Serde?

like image 234
Listerone Avatar asked May 23 '19 16:05

Listerone


3 Answers

serde_json provides types for generic JSON values with serde_json::Value:

use serde_json::Value;

// input variable
let input: &str = "{...}";

// parse into generic JSON value
let root: Value = serde_json::from_str(input)?;

// access element using .get()
let hostname: Option<&str> = root.get("data")
    .and_then(|value| value.get(0))
    .and_then(|value| value.get("hostname"))
    .and_then(|value| value.as_str());

// hostname is Some(string_value) if .data[0].hostname is a string,
// and None if it was not found
println!("hostname = {:?}", hostname); // = Some("a hostname")

(full playground example)

like image 93
Frxstrem Avatar answered Sep 28 '22 08:09

Frxstrem


If you want to panic if the value is missing or malformed, I'd use the Index syntax ([...]). If you want to handle the missing / malformed case, use the get method:

fn main() {
    let json_value = serde_json::json!({
      "data": [
        {
          "hostname": "a hostname"
        }
      ]
    });

    let host = &json_value["data"][0]["hostname"];
    println!("Host: {:?}", host);
}

You can also use a JSON Pointer via pointer:

let host = json_value.pointer("/data/0/hostname");
println!("Host: {:?}", host);
like image 26
Shepmaster Avatar answered Sep 28 '22 08:09

Shepmaster


I would chain flattened structures

use serde::{Serialize, Deserialize};
use serde_json::Value;

#[derive(Serialize, Deserialize)]
struct Payload {
    data: Vec<Data>,

    #[serde(flatten)]
    _: HashMap<String, Value>,
}

#[derive(Serialize, Deserialize)]
struct Data {
    hostname: String,

    #[serde(flatten)]
    _: HashMap<String, Value>,
}

let payload: Payload = serde_json::from_str(your_string)?;
assert_eq!(payload.data.0.hostname, "a hostname");
like image 26
gustavodiazjaimes Avatar answered Sep 28 '22 08:09

gustavodiazjaimes