JSON 與 dart:convert


Dart 本身的 dart:convert,就有 jsonEncode 可以將 Object 轉換為 JSON,以及 jsonDecode 能將 JSON 轉換為 Map<String, dynamic>

jsonEncode 的第一個參數可接受數字、布林、字串、nullListMap,例如,底下將 Map<String, dynamic> 轉換為 JSON:

var map = {
  'name' : 'Justin',
  'age'  : 40,
  'childs' : [
    {
      'name' : 'hamimi',
      'age'  : 3
    }
  ]
};

var json = jsonEncode(map);
print(json);   // 顯示 {"name":"Justin","age":40,"childs":[{"name":"hamimi","age":3}]}

因為 Dart 的 MapList 的字面表示方式,組合起來很像是 JSON 格式,透過以上方式來轉換 JSON 是蠻常見的做法,第一個參數也可以接受具有 toJson 方法的物件,由物件自身負責 JSON 轉換,例如若有個 User 類別如下:

class User {
  String name;
  int age;

  User({this.name, this.age});

  Map<String, dynamic> toJson() {
    return {
      'name': name,
      'age': age
    };
  }
}

就可以直接將 User 實例餵給 jsonEncode

var json = jsonEncode(User(name: 'Justin', age: '40'));
print(json);  // 顯示 {"name":"Justin","age":"40"}

如果類別沒有定義 toJson,可以在呼叫 jsonEncode 時指定 toEncodable,例如,假設方才的 User 類別沒有定義 toJson

var json = jsonEncode(User(name: 'Justin', age: '40'), toEncodable: (nonEncodable) {
  User user = nonEncodable;
  return {
    'name': user.name,
    'age': user.age
  };
});
print(json); // 顯示 {"name":"Justin","age":"40"}

jsonDecode 能將 JSON 轉換為 Map<String, dynamic>,例如:

var json = '''{
  "name": "Justin",
  "age": 40, 
  "childs": [
    {
      "age": 8,
      "name": "Irene"
    }
  ]
}''';

Map<String, dynamic> user = jsonDecode(json);
user.forEach((key, value) => print('${key}: ${value}'));

childs 的型態,也就是 user['childs'] 的型態會是 List<Map<String, dynamic>>jsonDecode 可以指定 reviver 函式,這會在每個被剖析出來的物件上呼叫,reviver 函式的參數是 keyvaluekey 會是字串、索引(List 的情況)或者是 null(整個剖析後的 Map<String, dynamic>),value 是對應的剖析結果物件。

預設的 reviver 函式,單純地傳回 value,你可以在 reviver 調整剖析後的結果,例如將字串轉大寫:

var json = '''{
  "name": "Justin",
  "age": 40, 
  "childs": [
    {
      "age": 8,
      "name": "Irene"
    }
  ]
}''';

Map<String, dynamic> user = jsonDecode(json, reviver: (key, value) {
  if(value is String) {
    return (value as String).toUpperCase();
  }
  return value;
});

// 字串會是大寫結果
user.forEach((key, value) => print('${key}: ${value}'));

在一些文件上,常見使用以下的模式,在物件模型與 Map<String, dynamic> 之間轉換:

class User {
  String name;
  int age;

  User({this.name, this.age});

  User.fromJson(Map<String, dynamic> json)
      : name = json['name'],
        age = json['age'];

  Map<String, dynamic> toJson() {
    return {
      'name': name,
      'age': age
    };
  }
}

這麼一來,就可以如下搭配 jsonEncodejsonDecode

var userMap = jsonEncode(User(name: 'Justin', age: 40));
var user = User.fromJson(jsonDecode(userMap));

只不過我個人感覺這有點奇怪,User.fromJson 其實應該命名為 User.fromMap,而 toJson 也應該命名為 toMap,雖然合理的解釋是「為了配合 jsonEncodejsonDecode 的 API 協定」,不過,比較好的方式,應該是物件模型與 JSON 之間的轉換,把 jsonEncodejsonDecode 封裝起來吧!

class User {
  String name;
  int age;

  User({this.name, this.age});

  User.fromJson(String json) {
    var decoded = jsonDecode(json);
    name = decoded['name'];
    age = decoded['age'];
  }

  String json() {
    return jsonEncode({
      'name': name,
      'age': age
    });
  }
}

如此一來,就可以如下直接轉換:

var json = User(name: 'Justin', age: 40).json();
var user = User.fromJson(json);