0

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>.

dtolnay
  • 9,621
  • 5
  • 41
  • 62
CuriOne
  • 103
  • 3
  • 8
  • @Shepmaster Well, I have shown the code that I have tried parsing this JSON output and had succeeded to a certain degree, by successfully parsing the number of people in space as well as the "people" array, now I would like to also show you how to get at the "name" key, but here I am getting a panic. I was under the impression that I had shown what I am trying to do in the code above, (that I in fact wrote), but I have now edited the question to provide additional detail. – CuriOne Aug 09 '15 at 21:42
  • @CuriOne you included a lot of code, that's true. And what you had did look good. The problem is that you didn't show any of your efforts to get the names out of the result. Your previous code was akin to "here's 40 lines of me *building* an array; how do I *sum* all the numbers in the array". It was lacking the crucial part of showing effort on the *sum* part. Anyway, you've edited it, so I'll delete my comments in a while. – Shepmaster Aug 09 '15 at 21:46
  • You say that line of code causes a panic, but it cannot even be compiled: *cannot index a value of type `&serde_json::value::Value`*. Please review how to create an [MCVE](/help/mcve). – Shepmaster Aug 09 '15 at 21:55

1 Answers1

2

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.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
  • Hmm, thanks for this, I think that I am going to play with this some more, but thanks for the suggestion. – CuriOne Aug 09 '15 at 21:59
  • This is a good answer and sort of an obvious one, the reason I got stuck is because I was expecting there to be a method to do that as part of getting the `"people"` values - something to keep it on one line. But this works great anyways. – CuriOne Aug 09 '15 at 22:55