Simplify a multi-level match situation

I am trying to decode a bunch of JSON files using rust-json. All JSON files I am interested has a structure like

{"traceEvents": [{}, {}, ...]}

and I want to filter out all irrelevant files. My current approach looks like

pub fn get_trace_events(json_object: &JsonValue) -> Result<&Vec<JsonValue>, Box<dyn Error>> {
    match json_object {
        JsonValue::Object(object) => {
            match object.get("traceEvents") {
                Some(trace_events_value) => {
                    match trace_events_value {
                        JsonValue::Array(array) => Ok(array),
                        _ => Err(Box::new(NotTraceEventError())),
                    }
                },
                None => Err(Box::new(NotTraceEventError())),
            }
        },
        _ => Err(Box::new(json::Error::WrongType(
            "Expecting an object".to_string(),
        ))),
    }
}

where I used plenty of matches. I am wondering if there is a way I can simplify the code.

In some cases (if there is no nesting), you can use if let and a tuple:

if let (Some(first), Some(second)) = (first_opt, second_opt) {
    ···
}

if let without a tuple is also useful when things are nested.

1 Like

Then can you deserialize into something with that structure? Then all the "missing fields" and "wrong types" kinds of problems are dealt with by the deserialization instead of you needing to write them yourself.

Maybe you could use something like #[derive(Deserialize)] struct Blah { traceEvents: Vec<JsonValue> }?

Alternatively, look for a json-path library that works with JsonValue, and make that do the traversal.

3 Likes

You can keep nesting patterns to flatten your match:

pub fn get_trace_events(json_object: &JsonValue) -> Result<&Vec<JsonValue>, Box<dyn Error>> {
    match json_object {
        JsonValue::Object(object) => {
            match object.get("traceEvents") {
                Some(JsonValue::Array(array)) => Ok(array),
                 _ => Err(Box::new(NotTraceEventError())),
            }
        },
        _ => Err(Box::new(json::Error::WrongType(
            "Expecting an object".to_string(),
        ))),
    }
}
2 Likes

Unfortunately there are such nesting..

1 Like

There are additional fields that might exist and are not interesting. Not sure if using a struct with only traceEvents fields will simplify the problem?

You don't need to add any fields you don't need to the struct. If you leave them out they'll be ignored when deserializing.

1 Like

Combinators can help reduce indentation if you have one path of successful nesting:

pub fn get_trace_events(json_object: &JsonValue) -> Result<&Vec<JsonValue>, Box<dyn Error>> {
  Ok(
    json_value_as_object(json_object)
      .ok_or(json::Error::WrongType("Expecting an object".to_string()))?
      .get("traceEvents")
      .ok_or(NotTraceEventError())?
  )
}

// I didn't find a similar function in rust_json crate, so we have to implement this ourself
fn json_value_as_object(json_value: &JsonValue) -> Option<&Object> {
  match(json_value) {
    JsonValue::Object(object) => Some(object),
    _ => None,
  }
}
1 Like