blob: e8a42c9c9fb4608aae89cfcc7410c1304625ea28 [file] [log] [blame]
// Copyright (c) 2017, Google Inc. Please see the AUTHORS file for details.
// All rights reserved. Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
import 'package:collection/collection.dart';
/// A JSON value.
///
/// This class is suitable for use in built_value fields. When serialized it
/// maps directly onto JSON values.
///
/// Deep operator== and hashCode are provided, meaning the contents of a
/// List or Map is used for equality and hashing.
///
/// List and Map classes are wrapped in [UnmodifiableListView] and
/// [UnmodifiableMapView] so they won't be modifiable via this object. You
/// must ensure that no updates are made via the original reference, as a
/// copy is not made.
///
/// Note: this is an experimental feature. API may change without a major
/// version increase.
abstract class JsonObject {
/// The value, which may be a bool, a List, a Map, a num or a String.
Object get value;
/// Whether the value is a [bool].
bool get isBool => false;
/// The value as a [bool], or throw if not.
bool get asBool => throw StateError('Not a bool.');
/// Whether the value is a [List].
bool get isList => false;
/// The value as a [List], or throw if not.
List get asList => throw StateError('Not a List.');
/// Whether the value is a [Map].
bool get isMap => false;
/// The value as a [Map], or throw if not.
Map get asMap => throw StateError('Not a Map.');
/// Whether the value is a [num].
bool get isNum => false;
/// The value as a [num], or throw if not.
num get asNum => throw StateError('Not a num.');
/// Whether the value is a [String].
bool get isString => false;
/// The value as a [String], or throw if not.
String get asString => throw StateError('Not a String.');
/// Instantiates with [value], which must be a bool, a List, a Map, a num
/// or a String. Otherwise, an [ArgumentError] is thrown.
factory JsonObject(Object? value) {
if (value is num) {
return NumJsonObject(value);
} else if (value is String) {
return StringJsonObject(value);
} else if (value is bool) {
return BoolJsonObject(value);
} else if (value is List<Object?>) {
return ListJsonObject(value);
} else if (value is Map<String, Object?>) {
return MapJsonObject(value);
} else if (value is Map) {
// Allow wrong type map, check individual values.
return MapJsonObject(value.cast());
} else {
throw ArgumentError.value(value, 'value',
'Must be bool, List<Object?>, Map<String?, Object?>, num or String');
}
}
JsonObject._();
@override
String toString() {
return value.toString();
}
}
/// A [JsonObject] holding a bool.
class BoolJsonObject extends JsonObject {
@override
final bool value;
BoolJsonObject(this.value) : super._();
@override
bool get isBool => true;
@override
bool get asBool => value;
@override
bool operator ==(dynamic other) {
if (identical(other, this)) return true;
if (other is! BoolJsonObject) return false;
return value == other.value;
}
@override
int get hashCode => value.hashCode;
}
/// A [JsonObject] holding a List.
class ListJsonObject extends JsonObject {
@override
final List<Object?> value;
ListJsonObject(List<Object?> value)
: value = UnmodifiableListView<Object?>(value),
super._();
@override
bool get isList => true;
@override
List<Object?> get asList => value;
@override
bool operator ==(dynamic other) {
if (identical(other, this)) return true;
if (other is! ListJsonObject) return false;
return const DeepCollectionEquality().equals(value, other.value);
}
@override
int get hashCode => const DeepCollectionEquality().hash(value);
}
/// A [JsonObject] holding a Map.
class MapJsonObject extends JsonObject {
@override
final Map<String, Object?> value;
MapJsonObject(Map<String, Object?> value)
: value = UnmodifiableMapView(value),
super._();
@override
bool get isMap => true;
@override
Map<String, Object?> get asMap => value;
@override
bool operator ==(dynamic other) {
if (identical(other, this)) return true;
if (other is! MapJsonObject) return false;
return const DeepCollectionEquality().equals(value, other.value);
}
@override
int get hashCode => const DeepCollectionEquality().hash(value);
}
/// A [JsonObject] holding a num.
class NumJsonObject extends JsonObject {
@override
final num value;
NumJsonObject(this.value) : super._();
@override
bool get isNum => true;
@override
num get asNum => value;
@override
bool operator ==(dynamic other) {
if (identical(other, this)) return true;
if (other is! NumJsonObject) return false;
return value == other.value;
}
@override
int get hashCode => value.hashCode;
}
/// A [JsonObject] holding a String.
class StringJsonObject extends JsonObject {
@override
final String value;
StringJsonObject(this.value) : super._();
@override
bool get isString => true;
@override
String get asString => value;
@override
bool operator ==(dynamic other) {
if (identical(other, this)) return true;
if (other is! StringJsonObject) return false;
return value == other.value;
}
@override
int get hashCode => value.hashCode;
}