3

I'm trying to parse a JSON file with the following loose format using serde_json in Rust:

{ "Source_n": { "Destination_n": { "distance": 2, "connections": [ { "color": "Any", "locomotives": 0, "tunnels": 0 } ] } ... 

where Source and Destination can be any number of keys (Link to full file).

I've created the following structs in an attempt to deseralize the JSON:

#[derive(Debug, Deserialize)] struct L0 { routes: HashMap<String, L1>, } #[derive(Debug, Deserialize)] struct L1 { destination_city: HashMap<String, L2>, } #[derive(Debug, Deserialize)] struct L2 { distance: u8, connections: Vec<L3>, } #[derive(Debug, Deserialize, Clone)] struct L3 { color: String, locomotives: u8, tunnels: u8, } 

When I try to read the JSON as an L0 object I get a panic on this line:

let data: L0 = serde_json::from_str(&route_file_as_string).unwrap(); 

Panic:

 Finished dev [unoptimized + debuginfo] target(s) in 0.01s Running `target/debug/ticket-to-ride` thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: Error("missing field `routes`", line: 1889, column: 1)', src/route.rs:39:64 stack backtrace: 0: rust_begin_unwind at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35/library/std/src/panicking.rs:517:5 1: core::panicking::panic_fmt at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35/library/core/src/panicking.rs:101:14 2: core::result::unwrap_failed at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35/library/core/src/result.rs:1617:5 3: core::result::Result<T,E>::unwrap at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35/library/core/src/result.rs:1299:23 4: ticket_to_ride::route::route_file_to_L0 at ./src/route.rs:39:20 5: ticket_to_ride::route::routes_from_file at ./src/route.rs:44:33 6: ticket_to_ride::main at ./src/main.rs:6:5 7: core::ops::function::FnOnce::call_once at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35/library/core/src/ops/function.rs:227:5 

I've been able to read the JSON as a HashMap<String, Value> object, but whenever I try to start working at the lower levels I get an error. It seems to be looking for a key named routes, but what I actually want to just a nested HashMap, similar to how you can read a JSON in Python in a nested fashion.

Any advice on how to proceed? Is what I'm attempting reasonable with this library?

4
  • As the error says ("missing field routes"), you are asking to deserialize an L0, which consists of just a routes element, but the string from which you try to deserialize is missing that element. The way you tell serde which element should be deserialized to which struct member is by giving them the same name. Commented May 18, 2022 at 12:56
  • 2
    You should either add #[serde(transparent)] to the two structs containing hash maps, or use #[serde(flatten)] on the hash map fields. Commented May 18, 2022 at 13:00
  • 1
    Also checkout #[serde(rename = "…")] . Or serde_json::Value, if you don't want rust structs for your JSON. Commented May 18, 2022 at 13:07
  • 1
    I think you could also directly deserialise to a HashMap<String, HashMap<String, L2>> without defining L0 and L1. Possibly with an alias for convenience. Commented May 18, 2022 at 13:51

2 Answers 2

6

As Sven Marnach says in their comment, add #[serde(flatten)] to create the HashMap from data that uses keys as JSON fields:

#[derive(Debug, Deserialize)] struct L0 { #[serde(flatten)] routes: HashMap<String, L1>, } #[derive(Debug, Deserialize)] struct L1 { #[serde(flatten)] destination_city: HashMap<String, L2>, } 
Sign up to request clarification or add additional context in comments.

1 Comment

Still working through parsing the JSON, but this has gotten me past my current blocking point. Thank you! I wasn't aware of #[serde(flatten)]
2

Functioning code parsing the referenced JSON is below. The demo function executes the parsing.

use serde::Deserialize; use std::collections::HashMap; use std::fs; use std::clone::Clone; #[derive(Debug, Deserialize)] pub struct L1 { #[serde(flatten)] destination_city: HashMap<String, L2>, } #[derive(Debug, Deserialize)] struct L2 { distance: u8, connections: Vec<L3>, } #[derive(Debug, Deserialize, Clone)] struct L3 { color: String, locomotives: u8, tunnels: u8, } fn route_file_to_hashmap(fpath: &str) -> HashMap<String, L1> { let route_file_as_string = fs::read_to_string(fpath).expect("Unable to read file"); let data: HashMap<String, L1> = serde_json::from_str(&route_file_as_string).unwrap(); return data; } pub fn routes_from_file(fpath: &str) -> HashMap<String, L1> { let route_file_as_map: HashMap<String, L1> = route_file_to_hashmap(fpath); return route_file_as_map; } pub fn demo() { let routes: HashMap<String, L1> = routes_from_file("usa.routes.json"); println!("---Cities---"); for (k, _) in &routes { println!("{}", k); } let chicago: &HashMap<String, L2> = &routes.get("Chicago").unwrap().destination_city; println!("---Destinations from Chicago---"); for (k, _) in chicago { println!("{}", k); } let to_omaha: &L2 = chicago.get("Omaha").unwrap(); println!("---Data on Route to Omaha---"); println!("Distance: {}", to_omaha.distance); print!("Connections: "); for c in &to_omaha.connections { println!("{} ", c.color); } } 

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.