1. nacika_ins

    Posted

    nacika_ins
Changes in title
+RustのJSONシリアライザをいろいろと試してみる(+α)
Changes in tags
Changes in body
Source | HTML | Preview
@@ -0,0 +1,228 @@
+この記事は、[Rustアドベントカレンダー 2015](http://qiita.com/advent-calendar/2015/rust-lang) 12日目の記事です。
+
+この記事では、様々なRustのJSONパーサとシリアライザを試してみた結果をご紹介しようと思います。
+
+## [serde-rs/json](https://github.com/serde-rs/json)
+serdeはRustの強力なシリアライズ用のフレームワークです。まだまだ開発中ではありますが、安定すればかなり期待できるフレームワークだと思います。
+
+### Usage
+
+```rust
+extern crate serde;
+extern crate serde_json;
+use std::collections::BTreeMap;
+fn main() {
+ let s = r#"{"x":1,"y":2}"#;
+ let deserialized_map: BTreeMap<String, f64> = serde_json::from_str(s).unwrap();
+ println!("{:?}", deserialized_map);
+ // => {"x": 1, "y": 2}
+ println!("{:?}", deserialized_map.get("x").unwrap());
+ // => 1
+}
+```
+
+シンプルに使うことが出来ます。しかし、予め型を指定しなければいけないので異なる型だと途端にエラーになります。
+
+```rust
+extern crate serde;
+extern crate serde_json;
+use std::collections::BTreeMap;
+fn main() {
+ extern crate serde;
+ extern crate serde_json;
+ use std::collections::BTreeMap;
+ let s = r#"{"x":1,"y": [1,2,3,4] }"#;
+ // let deserialized_map: BTreeMap<String, f64> = serde_json::from_str(s).unwrap();
+ // println!("{:?}", deserialized_map);
+ // => An unknown error occurred
+}
+```
+
+stable版以外だと、serde_macros を使用することが出来るのですが、stableは使用できないので複雑なことが難しいです。
+
+```rust
+extern crate serde;
+extern crate serde_json;
+use std::collections::BTreeMap;
+fn main() {
+ /* #![feature(plugin)] */
+ /* #![plugin(serde_macros)] */
+ // => serde_macros の コンパイルエラーが発生(stable releaseだと使えないらしい)
+}
+```
+
+## [jsonway](https://github.com/rustless/jsonway)
+
+DSLで書けるJSONシリアライザです。DSLなので、元のオブジェクトを変換するというわけではなさそうですね。
+
+### Usage
+
+```rust
+extern crate jsonway;
+fn main() {
+ let json = jsonway::object(|json| {
+ json.set("first_name", "Luke".to_string());
+ json.set("last_name", "Skywalker".to_string());
+
+ json.object("info", |json| {
+ json.set("homeworld", "Tatooine".to_string());
+ json.set("born", "19 BBY".to_string());
+ json.set("died", "Between 45 ABY and 137 ABY".to_string());
+ });
+
+ json.array("masters", |json| {
+ json.push("Obi-Wan Kenobi".to_string());
+ json.push("Yoda".to_string());
+ json.push("Joruus C'baoth (Briefly)".to_string());
+ json.push("Darth Sidious (Briefly)".to_string());
+ });
+ }).unwrap();
+ println!("{}", json);
+}
+```
+( [jsonway](https://github.com/rustless/jsonway) より引用 )
+
+とても簡潔に書くことが出来ます。
+
+## [json_macros](https://github.com/tomjakubowski/json_macros)
+
+こちらは、マクロを使ってJSON記法で書くことが出来る便利なライブラリです。しかしこちらもstable版Rustでは動きませんでした。
+
+### Usage
+
+```rust
+#![feature(plugin)] */
+#![plugin(json_macros)] */
+fn main() {
+extern crate "rustc-serialize" as rustc_serialize;
+let x = 123i32;
+println!("{}", json!({ // object literal
+ "foo": "foooooo", // string literal keys and values
+ "bar": [true, null, 123, 123.4], // array, boolean, null, numeric literals
+ "quux": { // nest as deeply as you like
+ "a": [1, 2, 3, 4],
+ "b": { "a": null },
+ "c": false
+ },
+ "waldo": (192 - x) // wrap in parens to splice ToJson expressions directly
+}).pretty().to_string());
+}
+```
+( [json_macros](https://github.com/tomjakubowski/json_macros)より引用 )
+
+## [json-rs](https://github.com/augustt198/json-rs)
+
+こちらは `#[feature]` を使用しており、 stable版Rustでは実行することが出来ませんでした。
+
+## [rustc_serialize](https://github.com/rust-lang-nursery/rustc-serialize)
+
+今一番Rustで使われているパーサとシリアライザはこれだと思います。
+指定した型に `RustcDecodable`、 `RustcEncodable` をderiveすると、その型のデータをシリアライズしたり、パースしたり出来ます。
+
+### Usage
+
+```rust
+extern crate rustc_serialize;
+use rustc_serialize::json::{Json, Parser};
+
+fn main () {
+ let mut parser = Parser::new(
+ r#"{
+ "a": 1.0,
+ "b": [
+ true,
+ "foo\nbar",
+ { "c": {"d": null} }
+ ]
+ }"#.chars()
+ );
+
+ parser.next();
+
+ let foo = parser.next().unwrap();
+ println!("{:?}", foo);
+ // => F64Value(1)
+}
+```
+
+nextで進めることによって、カーソルが変わり、取得できる値が変わる実装になっています。今回は F64Valueの型(Enumの値)を取得したのですが、中に入っているf64を取得する方法は見つけられませんでした。
+
+通常は、予め構造体を定義し、その構造体にマップする使い方のほうが多いと思います。
+
+```rust
+extern crate rustc_serialize;
+use rustc_serialize::json;
+use std::collections::HashMap;
+
+#[derive(Debug, RustcDecodable, RustcEncodable)]
+struct Foo {
+ bar: HashMap<String, String>
+}
+
+fn main () {
+ let mut baz = HashMap::new();
+ baz.insert("test".to_owned(), "test".to_owned());
+ let foo = Foo { bar: baz };
+ let enc = json::encode(&foo).unwrap();
+ println!("{:?}", enc);
+ //=> "{\"bar\":{\"test\":\"test\"}}"
+ let dec: Foo = json::decode(&enc).unwrap();
+ println!("{:?}", dec);
+ //=> Foo { bar: {"test": "test"} }
+}
+
+```
+
+## 最後に「type」 について
+
+構造体を定義する方法でrustc_serializeを使用していたのですが、構造体では type というfieldは使用できないことに気づきました。serde ではマクロにJSONのキーと別なキー名をマップさせるマクロがあるのですが、stable版Rustでは使用できないので、辛いです。
+
+JavaScriptの JSON.parse や Python の json.loads くらい楽なものはないかと思い、探してはみたものの見つかず、辛くなってきたので
+
+## JSONパーサを作りました
+
+[JsonFlex](https://github.com/nacika-ins/json_flex) と言います。(crates.ioは[こちら](https://crates.io/crates/json_flex))
+
+[RustのSlackチャンネル](https://rust-jp.slack.com/messages) の方にも色々と教えて頂きながらなんとか完成させることが出来ました。
+
+またまだベータ版でテストも不十分なところがありますが、大体のJSONはパースすることが出来ます。ここで簡単なご紹介をさせていただきたいとおもいます。
+
+### Usage
+
+```rust
+use json_flex;
+use json_flex::{JFObject, Unwrap};
+use std::collections::HashMap;
+
+fn main () {
+
+ let jf = json_flex::decode(r#"["a", "b", "c", ["a", "b", "c"], "d", ["ABC"],[1,2]]"#.to_owned());
+ let jft = format!("{:?}", jf);
+ assert!(jft == r#"Array([String("a"), String("b"), String("c"), Array([String("a"), String("b"), String("c")]), String("d"), Array([String("ABC")]), Array([Integer(1), Integer(2)])])"#);
+ println!("{}", jf.to_json());
+
+}
+```
+
+このように、JavaScriptのオブジェクトのように、 `foo[0][0]["baz"]` といった記法で書くことが出来ます。このアプローチは、 [アドベントカレンダー5日目のインデックス構文によるアクセスを実装する](http://qiita.com/szktty/items/9fa3b972fbdae7aaa0ef) を参考にさせて頂きました。ありがとうございます。
+
+JsonFlex は Enum列挙体による8個の値からなるデータの共用体です。
+
+```rust
+pub enum JFObject {
+ String(String),
+ Integer(i64),
+ Float(f64),
+ Dictionary(HashMap<String, JFObject>),
+ Array(Vec<JFObject>),
+ Null,
+ False,
+ True,
+}
+```
+
+Null型とFalse型とTrue型は、中に要素を持たないですが、それ以外の型は中にRustの基本的な型を持ちます。それぞれ、`unwrap_string`等のメソッドで中身を取り出す事ができます。本当は `unwrap` のみで取り出したかったのですが、型がうまく推論できない場面があり、このような実装になっています。
+
+しかし Indexを使用する場合は、十分な型推論が出来るため、`foo[0][0]["baz"]`と言った書き方が可能になりました。
+
+