RustでJSONを扱う(serde_json) 不定形のJSON
サンプル
自由な形状のJSONを読み込む
前記事でファイルからJSONを読み込めるとなったら、構造が定まらないJSONがくる場合がある。
またJSONに何のkeyが足りないのか細かくエラーを出したいのであれば Deserialize
では厳しくなってくる。
様々な形のJSONに対応すべく
-
Vec
を含むDeserialize
-
serde_json::Value
で読み込む -
serde_json::Value
で値を取得する
という3構成で見ていく。
Vec
を含む Deserialize
サンプル GitHub: 02_flex
前からやっている Deserialize
に Vec
を使うことで可変長なデータを使うことができる。
まったく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
で読み込む
さて本当に形状がわからない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>
と File
の BufReader
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!
で出力してやれば map
な serde_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
で値を取得する
先ほど読み込んだ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を起こすので慎重に使う必要がある。
Discussion