Listerone
Listerone

Reputation: 1631

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

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?

Upvotes: 16

Views: 15840

Answers (3)

Freyja
Freyja

Reputation: 40914

The serde_json crate 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)

Upvotes: 18

Shepmaster
Shepmaster

Reputation: 432089

If you are OK with returning Value::Null if the value is missing or malformed, I'd use the Index syntax ([...]). If you want to handle the missing / malformed case differently, 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);

Upvotes: 8

gustavodiazjaimes
gustavodiazjaimes

Reputation: 2459

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");

Upvotes: 2

Related Questions