blob: 60c7d56a57e7006a40e943685a706f353f604704 [file] [log] [blame]
// Copyright 2017 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// The whole point of this file is to wrap dynamic calls in a pretense of type safety, so we use dynamic calls a lot.
// Also the code uses a lot of one-line flow control so we don't bother wrapping them all in blocks.
// ignore_for_file: avoid_dynamic_calls, curly_braces_in_flow_control_structures
import 'dart:convert' as dart show json;
import 'package:meta/meta.dart';
@immutable
class Json {
factory Json(dynamic input) {
if (input is Json)
return Json._wrap(input._value);
return Json._wrap(input);
}
factory Json.list(List<dynamic> input) {
return Json._raw(input.map<Json>(Json._wrap).toList());
}
// (This differs from "real" JSON in that we don't allow duplicate keys.)
factory Json.map(Map<dynamic, dynamic> input) {
final Map<String, Json> values = <String, Json>{};
input.forEach((dynamic key, dynamic value) {
final String name = key.toString();
assert(!values.containsKey(name), 'Json.map keys must be unique strings');
values[name] = Json._wrap(value);
});
return Json._raw(values);
}
factory Json.parse(String value) {
return Json(dart.json.decode(value));
}
const Json._raw(this._value);
factory Json._wrap(dynamic value) {
if (value == null)
return const Json._raw(null);
if (value is num)
return Json._raw(value.toDouble());
if (value is List)
return Json.list(value);
if (value is Map)
return Json.map(value);
if (value == true)
return const Json._raw(true);
if (value == false)
return const Json._raw(false);
if (value is Json)
return value;
return Json._raw(value.toString());
}
final dynamic _value;
dynamic unwrap() {
if (_value is Map)
return toMap();
if (_value is List)
return toList();
return _value;
}
bool get isMap => _value is Map;
bool get isList => _value is List;
bool get isScalar => _value == null || _value is num || _value is bool || _value is String;
Type get valueType => _value.runtimeType;
Map<String, dynamic> toMap() {
final Map<String, dynamic> values = <String, dynamic>{};
if (_value is Map) {
_value.forEach((String key, Json value) {
values[key] = value.unwrap();
});
} else if (_value is List) {
for (int index = 0; index < (_value as List<dynamic>).length; index += 1)
values[index.toString()] = _value[index].unwrap();
} else {
values['0'] = unwrap();
}
return values;
}
List<dynamic> toList() {
if (_value is Map)
return (_value as Map<String, Json>).values.map<dynamic>((Json value) => value.unwrap()).toList();
if (_value is List)
return (_value as List<Json>).map<dynamic>((Json value) => value.unwrap()).toList();
return <dynamic>[unwrap()];
}
dynamic toScalar() {
assert(isScalar, 'toScalar called on non-scalar. Check "isScalar" first.');
return _value;
}
Iterable<Json> asIterable() {
if (_value is Map)
return (_value as Map<String, Json>).values.toList();
if (_value is List)
return _value as List<Json>;
return const <Json>[];
}
double toDouble() => _value as double;
int toInt() => (_value as double).toInt();
bool toBoolean() => _value as bool;
@override
String toString() => _value.toString();
String toJson() {
return dart.json.encode(unwrap());
}
dynamic operator [](dynamic key) {
return _value[key];
}
void operator []=(dynamic key, dynamic value) {
_value[key] = Json._wrap(value);
}
bool hasKey(String key) {
return _value is Map && (_value as Map<String, Json>).containsKey(key);
}
@override
dynamic noSuchMethod(Invocation invocation) {
if (invocation.isGetter) {
final String name = _symbolName(invocation.memberName);
if (_value is Map) {
if ((_value as Map<String, Json>).containsKey(name))
return this[name];
return const Json._raw(null);
}
}
if (invocation.isSetter)
return this[_symbolName(invocation.memberName, stripEquals: true)] = invocation.positionalArguments[0];
return super.noSuchMethod(invocation);
}
// Workaround for https://github.com/dart-lang/sdk/issues/28372
String _symbolName(Symbol symbol, { bool stripEquals = false }) {
// WARNING: Assumes a fixed format for Symbol.toString which is *not*
// guaranteed anywhere.
final String s = '$symbol';
return s.substring(8, s.length - (2 + (stripEquals ? 1 : 0)));
}
bool operator <(Object other) {
if (other is Json)
return _value < other._value as bool;
return _value < other as bool;
}
bool operator <=(Object other) {
if (other is Json)
return _value <= other._value as bool;
return _value <= other as bool;
}
bool operator >(Object other) {
if (other is Json)
return _value > other._value as bool;
return _value > other as bool;
}
bool operator >=(Object other) {
if (other is Json)
return _value >= other._value as bool;
return _value >= other as bool;
}
dynamic operator -(Object other) {
if (other is Json)
return _value - other._value;
return _value - other;
}
dynamic operator +(Object other) {
if (other is Json)
return _value + other._value;
return _value + other;
}
dynamic operator /(Object other) {
if (other is Json)
return _value / other._value;
return _value / other;
}
dynamic operator ~/(Object other) {
if (other is Json)
return _value ~/ other._value;
return _value ~/ other;
}
dynamic operator *(Object other) {
if (other is Json)
return _value * other._value;
return _value * other;
}
dynamic operator %(Object other) {
if (other is Json)
return _value % other._value;
return _value % other;
}
dynamic operator |(Object other) {
if (other is Json)
return _value.toInt() | other._value.toInt();
return _value.toInt() | other;
}
dynamic operator ^(Object other) {
if (other is Json)
return _value.toInt() ^ other._value.toInt();
return _value.toInt() ^ other;
}
dynamic operator &(Object other) {
if (other is Json)
return _value.toInt() & other._value.toInt();
return _value.toInt() & other;
}
dynamic operator <<(Object other) {
if (other is Json)
return _value.toInt() << other._value.toInt();
return _value.toInt() << other;
}
dynamic operator >>(Object other) {
if (other is Json)
return _value.toInt() >> other._value.toInt();
return _value.toInt() >> other;
}
@override
bool operator ==(Object other) {
if (other is Json)
return _value == other._value;
return _value == other;
}
@override
int get hashCode => _value.hashCode;
}