🐻

RustでJSONを扱う(serde_json) 不定形のJSON

2023/08/13に公開

サンプル

自由な形状のJSONを読み込む

前記事でファイルからJSONを読み込めるとなったら、構造が定まらないJSONがくる場合がある。
またJSONに何のkeyが足りないのか細かくエラーを出したいのであれば Deserialize では厳しくなってくる。
様々な形のJSONに対応すべく

  • Vec を含む Deserialize
  • serde_json::Value で読み込む
  • serde_json::Value で値を取得する

という3構成で見ていく。

Vec を含む Deserialize

サンプル GitHub: 02_flex

前からやっている DeserializeVec を使うことで可変長なデータを使うことができる。
まったくJSONの形がわからないわけではないが要素数がわからない場合は Deserialize でも十分に対応できる。

構造体

下記の例では "Band" 構造体に "Member" 構造体が Vec として入っている。バンドメンバーの人数はグループによって違うわけでこのような構造が考えられる。

#[derive(Deserialize, Debug)]
struct Member {
    name: String,
    age: u32,
}

#[derive(Deserialize, Debug)]
struct Band {
    band_name: String,
    member: Vec<Member>,
}

JSONの形状は以下のようなものを想定している。
"member" キーには配列が使われており、同様のデータ形状を複数含まれるだろう。

{
  "band_name": "Kessoku Band",
  "member":[
    {
      "name": "Nijika",
      "age": 17
    },
    {
      "name": "Ryo",
      "age": 16
    },
    {
      "name": "Hitori",
      "age": 15
    },
    {
      "name": "Ikuyo",
      "age": 16
    }
  ]
}

読み込み

普通の構造体と同じようにデータを読み込む。

let file: Result<File, std::io::Error> = File::open(FILE_PATH);
let file = match file {
    Ok(o) => o,
    Err(e) => {
        println!("Failed to open file ({}).", FILE_PATH);
        return Err(Box::new(e));
    }
};
let reader: BufReader<File> = BufReader::new(file);

let json: Result<Band, serde_json::Error> = from_reader::<BufReader<File>, Band>(reader);

表示

for文を使ってそれぞれのデータを読みだしてみる。

println!("{}", json.band_name);
println!("  member");
for i in json.member.iter() {
    println!("  - {:<8} ({})", i.name, i.age);
}

以下のように表示される。

Kessoku Band
  member
    - Nijika   (17)
    - Ryo      (16)
    - Hitori   (15)
    - Ikuyo    (16)

serde_json::Value で読み込む

サンプル GitHub: 03_flex_value

さて本当に形状がわからないJSONが来た時にどうするかということである。

serde_json では serde_json::Value という 列挙型 で多分木を構成する。
serde_json::Value の定義は以下のようになっている。

enum Value {
    Null,
    Bool(bool),
    Number(Number),
    String(String),
    Array(Vec<Value>),
    Object(Map<String, Value>),
}

パースした結果一番上の階層は Map<String, Value>Object になり、その先に様々な String をキーとした serde_json::Value のバリューがぶら下がる。

読み込み

from_reader::<BufReader<File>, Value>FileBufReader BufReader<File> から Value ( serde_json::Value )として変換する。

let json: Result<Value, serde_json::Error> = from_reader::<BufReader<File>, Value>(reader);
let json = match json {
    Ok(o) => o,
    Err(e) => {
        println!("Failed to parse json.");
        return Err(Box::new(e));
    }
};

ところで、これまで冗長に書いてきたがRustは型の推論をしてくれるので

let json: Result<Value, serde_json::Error> = from_reader::<BufReader<File>, Value>(reader);

と書かなくても

let json = from_reader::<BufReader<File>, Value>(reader);

というように 「BufReader<File> から serde_json::Value に変換しろよ」と書くか

let json: Result<Value, serde_json::Error> = from_reader(reader);

と 「serde_json::Value を返せよ」というように書けば問題なくビルドすることができる。

ただし、

let json = from_reader(reader);

だけだとどういう型に変換して欲しいのかわからないのでエラーを吐く場合がある。

出力

println! で出力してやれば mapserde_json::Value::Object から始まるデータ形状を取得できていることがわかる。

println!("{:?}", json);
Object {"band": Array [Object {"band_name": String("Kessoku Band"), "member": Array [Object {"age": Number(17), "name": String("Nijika")}, Object {"age": Number(16), "name": String("Ryo")}, Object {"age": Number(15), "name": String("Hitori")}, Object {"age": Number(16), "name": String("Ikuyo")}]}], "live_music_club": String("STARRY"), "tencho": Object {"age": Number(29), "name": String("Seika")}}

serde_json::Value で値を取得する

サンプル GitHub: 04_flex_value-02

先ほど読み込んだJSONを println! で雑に出力するのではなく、実際にを取り出してみる。

一番上の階層を取得する

前述通り、JSONを serde_json::Value で取り出せばまず serde_json::Value::Object から始まる。
その値を取り出してみる。

if let 文でmapとして値を取り出し、for文で回してキーのみを取り出している。

if let Value::Object(map) = &json {
    for (key, _value) in map {
        println!("  - {}", key);
    }
}
Top level keys
  - band
  - live_music_club
  - tencho

[] (square brackets)を使う

println!(
    "  json[\"band\"][0][\"member\"][3]: {}",
    json["band"][0]["member"][3]
);
println!(
    "  json[\"band\"][1][\"member\"][3] (not exist): {}",
    json["band"][1]["member"][3]
);
Display value
  json["band"][0]["member"][3]: {"age":16,"name":"Ikuyo"}
  json["band"][1]["member"][3] (not exist): null

1文目は "band" 1つ目の member の4人目の値を取り出し、 serde_json::Value::Object が取り出されている。
しかし2文目は 2つ目 の "band" のメンバーを見ようとしているがそもそも2つ目の "band" が登録されていないので serde_json::Value::Null が返ってきている。

if let Value::Null = json["band"][1]["member"][3] {
    println!("  json[\"band\"][1][\"member\"][3] has no value");
}

とやれば引っかかってくる。

getを使う

以下のように値を取り出すことができる。

println!(
    "  json[\"band\"][0][\"member\"].get(3).unwrap(): {}",
    json["band"][0]["member"].get(3).unwrap()
);
println!(
    "  json[\"band\"][0].get(\"member\").unwrap()[3]: {}",
    json["band"][0].get("member").unwrap()[3]
);

[] (square brackets) との違いとしてOption<&Value> が返ってくるため、処理してやらなければならない。
1文目だと json["band"][0]Option::None の場合は .unwrap() ではpanicを起こすので慎重に使う必要がある。

Reference

Discussion