Subterranean Flower

DartにおけるJSONの扱い方

Author
古都こと
ふよんとえんよぷよぐやま!!!

Dartはウェブでの利用を目的として開発された言語だ。当然、JSONも扱うことができる。しかしながらネット上にはろくな情報が無く、見つけたとしても情報が古い(正式版に至るまでに破壊的変更があったので、現在の仕様とは異なる)ことが多い。

「JSON周りのことぐらい、マニュアルを適当に流し読みすれば、すぐにわかるじゃないか」と言われてしまうと返す言葉もないが、それにしても日本語での情報があまりにも少なかったため、簡単にまとめることにした。

基本

dart:convertには文字エンコードなどに関するクラスが含まれている。JSON関係のクラスも含まれているので、これを利用する。

オブジェクト→JSON文字列

オブジェクトからJSON文字列への変換は、JsonCodecクラスのencodeメソッドを用いて変換する。いくつかの制限はあるものの、基本的にプリミティブ型(※注1)やコレクションであれば変換は可能である。dart:convertライブラリにはJsonCodecのインスタンスが含まれているので、新たに生成する必要はない。

(※注1)Dartにおいて、例えばintやdoubleは単なるインターフェイスにすぎないが、便宜上「プリミティブ型」と表記する。

import 'dart:convert';

void main() {
  // 一部を除き、プリミティブ型なら変換可能
  // ここでは使用していないが、 null, num, double, bool なども可
  String encodableStr  = "test";
  int    encodableInt  = 100;
  List   encodableList = ["value", "value2", 1, 2];
  Map    encodableMap  = {"Str":"value", "int":123, "List":["a",1], "Map":{"key":"value"}};

  // encodeメソッドにオブジェクトを渡せば、JSON文字列が得られる
  String encodedStr  = JSON.encode(encodableStr);
  String encodedInt  = JSON.encode(encodableInt);
  String encodedList = JSON.encode(encodableList);
  String encodedMap  = JSON.encode(encodableMap);

  print("String:$encodedStr");
  print("int:$encodedInt");
  print("List:$encodedList");
  print("Map:$encodedMap");
}
$ dart json_encode_test.dart
String:"test"
int:100.0
List:["value","value2",1,2]
Map:{"Str":"value","int":123,"List":["a",1],"Map":{"key":"value"}}

JSON文字列→オブジェクト

JSON文字列からオブジェクトへの変換はdecodeメソッドを用いて行う。decodeメソッドはJSON文字列に応じた型を返すので注意。

import 'dart:convert';

void main() {
  String parsableStr  = '"string value"';
  String parsableList = '[1,2,3,"a","b","c"]';
  String parsableMap  = '{"key":"value", "key2":1234}';

  // decodeメソッドはJSON文字列に応じた型を返す。
  String parsedStr  = JSON.decode(parsableStr);
  List   parsedList = JSON.decode(parsableList);
  Map    parsedMap  = JSON.decode(parsableMap);

  print("String:$parsedStr");
  print("List:$parsedList");
  print("Map:$parsedMap");
}
$ dart json_decode_test.dart
String:string value
List:[1, 2, 3, a, b, c]
Map:{key: value, key2: 1234}

Map型をJSON文字列へ変換する際の制約

Map型をJSON文字列へ変換するには、キーがString型である必要がある。Object型はもちろん、int型などをキーにしていても変換はできない。

import 'dart:convert';

void main() {
  Map<String, dynamic> encodableMap   = {"key1":"value", "key2":123};
  Map<int, dynamic>    unencodableMap = {10:"value1", 20:"value2"};

  // キーがString型であれば問題は無い
  String encodedMap = JSON.encode(encodableMap);

  // キーをint型にすると、TypeErrorが投げられる
  try { String unencodedMap = JSON.encode(unencodableMap); }
  on TypeError catch(e) {
    print(e);
    print("Key must be String");
  }
}
$ dart map_encode_test.dart
type 'int' is not a subtype of type 'String' of 'key'.
Key must be String

カスタムクラスの変換

JsonCodec.encodeが変換できるのは、一部の型に限る。自作のカスタムクラスを変換したい場合は、クラスにtoJsonメソッドを実装する必要がある。toJsonメソッドは、JSON文字列へ変換可能なオブジェクトを返すメソッドで、JsonCodec.encodeはこれを実行する。インターフェイスは提供されていないので、必要であれば自作しておくと良い。

import 'dart:convert';

void main() {
  Person         p1  = new Person("Yukari Tamura", 17);
  EncodablePerson p2 = new EncodablePerson("Yukari Tamura", 17);

  // toJsonがないクラスはencodeできない
  try { print("Person:${JSON.encode(p1)}"); } on JsonUnsupportedObjectError { print("Error"); }

  // toJsonがあればencodeできる
  print("Person:${JSON.encode(p2)}");
}

class Person {
  String name;
  int    age;
  Person(this.name, this.age);
}

class EncodablePerson {
  String name;
  int    age;

  EncodablePerson(this.name, this.age);

  // toJsonメソッドを実装しておくとencode時に呼ばれる
  // JSON文字列へと変換可能なオブジェクトを返す
  // ここではMap型を返している
  dynamic toJson() => {"name":name, "age":age};
}
$ dart tojson_test.dart
Error
Person:{"name":"Yukari Tamura","age":17}

toEncodable

JsonCodec.encodeのtoEncodableパラメータを指定することにより、変換不可能なオブジェクトが渡されたときの挙動を指定することができる。

import 'dart:convert';

void main() {
  Object unencodable = new Object();

  // そのままではエラー
  try { print(JSON.encode(unencodable)); } on JsonUnsupportedObjectError { print("Error"); }

  // toEncodableで文字列に変換している
  try { print(JSON.encode(unencodable,toEncodable:(e)=>e.toString())); } on JsonUnsupportedObjectError { print("Error"); }
}
$ dart toencodable_test.dart
Error
"Instance of 'Object'"

reviver

JsonCodec.decodeのreviverパラメータを指定することにより、デコード時の挙動を指定することができる。dynamic reviver(dynamic key, dynamic value)は各要素がデコードされるたびに呼ばれる。keyがnullである場合は、valueに最終結果が格納されている。

import 'dart:convert';

void main() {
  String decodable = '{"key1":"value", "key2":123, "key3":{"key4":"value2", "key5":456}}';

  // デコードしながら各key-valueを表示するreviver
  dynamic rev(key,value) { print("Key:$key, Value:$value"); return value; }

  // reviverを指定してデコードする
  Map map = JSON.decode(decodable, reviver:rev);
  print("Map:$map");
}
$ dart reviver_test.dart
Key:key1, Value:value
Key:key2, Value:123
Key:key4, Value:value2
Key:key5, Value:456
Key:key3, Value:{key4: value2, key5: 456}
Key:null, Value:{key1: value, key2: 123, key3: {key4: value2, key5: 456}}
Map:{key1: value, key2: 123, key3: {key4: value2, key5: 456}}