CuriOne
CuriOne

Reputation: 103

Selecting a subset of keys from a JSON array

I am trying to parse a JSON API that spits out output like this:

{
  "message": "success", 
  "number": 6, 
  "people": [
    {
      "craft": "ISS", 
      "name": "Gennady Padalka"
    }, 
    {
      "craft": "ISS", 
      "name": "Mikhail Kornienko"
    }, 
    {
      "craft": "ISS", 
      "name": "Scott Kelly"
    }, 
    {
      "craft": "ISS", 
      "name": "Oleg Kononenko"
    }, 
    {
      "craft": "ISS", 
      "name": "Kimiya Yui"
    }, 
    {
      "craft": "ISS", 
      "name": "Kjell Lindgren"
    }
  ]
}

Source: http://api.open-notify.org/astros.json

I'm using serde for this and have managed to come up with the following code so far:

extern crate curl;
extern crate serde_json;

use curl::http;
use std::str;
use serde_json::{from_str};

fn main() {
    // Fetch the data
    let response = http::handle()
       .get("http://api.open-notify.org/astros.json")
       .exec().unwrap();

     // Get the raw UTF-8 bytes
     let raw_bytes = response.get_body();
     // Convert them to a &str
     let string_body: &str = str::from_utf8(&raw_bytes).unwrap();

     // Get the JSON into a 'Value' Rust type
     let json: serde_json::Value = serde_json::from_str(&string_body).unwrap();

     // Get the number of people in space
     let num_of_ppl: i64 = json.find_path(&["number"]).unwrap().as_i64().unwrap();
     println!("There are {} people on the ISS at the moment, they are: ", num_of_ppl);

     // Get the astronauts
     // Returns a 'Value' vector of people
     let ppl_value_space = json.find_path(&["people"]).unwrap();
     println!("{:?}", ppl_value_space);
}

Now, ppl_value_space gets me this, as expected:

[{"craft":"ISS","name":"Gennady Padalka"}, {"craft":"ISS","name":"Mikhail Kornienko"}, {"craft":"ISS","name":"Scott Kelly"}, {"craft":"ISS","name":"Oleg Kononenko"}, {"craft":"ISS","name":"Kimiya Yui"}, {"craft":"ISS","name":"Kjell Lindgren"}]

But, I want to get to the "name" key, as to essentially have something like:

[{"name":"Gennady Padalka"}, {"name":"Mikhail Kornienko"}, {"name":"Scott Kelly"}, {"name":"Oleg Kononenko"}, {"name":"Kimiya Yui"}, {"name":"Kjell Lindgren"}]

So as to be able to get only the names of the astronauts currently in space.

How do I get the "name" within "people", without the "craft"?

I tried to get to name like so:

ppl_value_space[0].find_path(&["name"]).unwrap();

But it ends with a panic, which basically means that the key is None, since I unwrap() an Option<T>.

Upvotes: 0

Views: 1981

Answers (1)

Shepmaster
Shepmaster

Reputation: 430634

This worked for me:

if let &Value::Array(ref people) = ppl_value_space {
    let names = people.iter().filter_map(|person| person.find_path(&["name"]));
    for name in names {
        println!("{:?}", name);
    }
}

Since a serde_json::Value is an enum, it can be many different types of values. And array is just one of those, it could be other things like a string or a number. We expect it to be an array, but Rust forces us to think about the other cases.

In this case, we ignore all types except for a Value::Array by using an if-let statement. When the condition is true we get a reference to the contained array.

We iterate over each item in the array and find the name object inside of it. filter_map is used to ignore None values, but you may want to do something different.

Each value is printed out, but you could also collect them into a new Vec or something more exciting.

Upvotes: 2

Related Questions