If let alternative?

I am a beginner at rust, so...

I was working with some json data today, reading and extracting data from it, using serde_json. In many cases I got a jason node as a serde_json::Value, which is an option type. I know that it was a Vallue::Array(v) type but had to keep writing stuff like this:

    if let Value::Array(items) = items {
        for item in items {
            /// do something with each item
        }
    }

Is this the only way to do this? It is very verbose. Is there a better way the is more rust-ish?

(and I know that I could read json into real objects, but that is not the issue here)

Thanks.

Generally no. However most enum types will have functions that help you with this

Add an iterator adapter and you can reduce it to one loop:

for item in items.as_array().flatten() {
  // do something
}

flatten turns the Option<&Vec<Value>> into an iterator over the values in the array.
This works because &Vec<Value> implemnts IntoIterator<Item=&Value>.

1 Like

I believe you might have made a mistake, you can't call .flatten() on an Option<&Vec<T>>:

fn main() {
    let vec = Vec::<usize>::new();
    let foo = Some(&vec);
    for i in foo.flatten() {
        println!("{:?}", i);
    }
}

Playground

However, this can be fixed by saying:

fn main() {
    let vec = vec![1, 2, 3];
    let foo = Some(&vec);
    for i in foo.into_iter().flatten() {
        println!("{:?}", i);
    }
}

Playground

Since Option<T> implements IntoIterator<Item = T> (it produces an iterator with either one or no elements), calling .into_iter() turns it into an Iterator<Item = &Vec<T>>. Calling .flatten() on an iterator of things which can be turned into iterators (&Vec<T> implements IntoIterator<Item = &T>) will yield an iterator over items &T.

This approach leaves out any kind validation in the case it isn't an array, so I might suggest you say instead:

for item in items.as_array().unwrap() {
    // Do something with item
}

Since that will panic if it isn't an array.

1 Like

Thanks. I confused myself a bit (and did not test it!)

Right. I was going for the same behaviour as the original code (do nothing if it isn't an array).

1 Like

If you just need the value inside the Value::Array variant, you can just extract that into a new variable and use it normally.
So, for example, something along these lines:

// we know `items` is a `Value::Array`, so extract the data inside
let items: Vec<Value> = match items {
    Value::Array(items) => items,
    _ => unreachable!(), // we think this is unreachable
};

// do whatever you want with the items

for item in &items {
    // cool stuff here
}

playground link

Yes there is. Generally, when you know the structure of the JSON beforehand, you shouldn't manipulate it like this, using a Value that is effectively dynamically-typed.

Instead, you should use Serde for automatically parsing the JSON into a strongly-typed value that you define for yourself. This makes it possible to validate the structure of the JSON upfront, once, while parsing it.

Once that succeeds, subsequent accesses to the information will go through your own model types, obviating the need for verbose, manual checking.

5 Likes

Thanks for all the answers, they were very helpful.