解析嵌套的JSON对象

3

我正在尝试使用Rust中的serde_json解析以下松散格式的JSON文件:

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

其中SourceDestination可以是任意数量的键(完整文件链接)。

我创建了以下结构体,试图将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,
}

当我试图将JSON作为L0对象读取时,我在这一行上遇到了紧急情况:
let data: L0 = serde_json::from_str(&route_file_as_string).unwrap();

惊恐:
    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

我能够读取JSON并将其转换为HashMap<String, Value>对象,但是当我尝试在更低的层次上工作时,就会出现错误。它似乎正在寻找一个名为routes的键,但我实际上想要的只是类似于Python中嵌套读取JSON的方式来读取嵌套的HashMap。
有什么建议吗?使用这个库进行我的尝试是否合理?

正如错误所述(“缺失字段 routes”),您正在尝试反序列化一个仅包含 routes 元素的 L0,但您试图反序列化的字符串中缺少该元素。您告诉 serde 哪个元素应反序列化到哪个结构成员的方式是通过给它们相同的名称。 - hkBst
2
你应该在包含哈希映射的两个结构体上添加 #[serde(transparent)],或者在哈希映射字段上使用 #[serde(flatten)] - Sven Marnach
1
还要查看#[serde(rename = "…")] . 如果你不想为JSON使用Rust结构,则可以使用serde_json :: Value - Caesar
1
我认为你也可以直接反序列化为 HashMap<String, HashMap<String, L2>>,而不需要定义 L0 和 L1。可能还可以为方便起见设置别名。 - Masklinn
2个回答

5

正如Sven Marnach在评论中所说,添加#[serde(flatten)]可从使用JSON字段作为键的数据创建HashMap:

#[derive(Debug, Deserialize)]
struct L0 {
    #[serde(flatten)]
    routes: HashMap<String, L1>,
}

#[derive(Debug, Deserialize)]
struct L1 {
    #[serde(flatten)]
    destination_city: HashMap<String, L2>,
}

仍在处理解析JSON的过程中,但这已经帮助我解决了当前的阻塞点。谢谢!我之前不知道有#[serde(flatten)] - A. Gnias

2

以下是能解析所引用JSON的可运行代码。 demo 函数执行解析。

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);
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接