Created
March 28, 2022 02:43
-
-
Save matanlurey/c2deff6318e00b96b5a1d5af8e5c6aa6 to your computer and use it in GitHub Desktop.
A set of type-safe (Dart) JSON helpers that provide better error message handling.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/// A set of type-safe JSON helpers that provide better error message handling. | |
/// | |
/// When used on well-structured JSON, these helpers have minimal overhead over | |
/// using `as` casts (i.e. `json['foo'] as String); however, when the cast fails | |
/// (which, should be rare in well-structured JSON) a format exception that | |
/// tries to highlight the offending source range is shown. | |
/// | |
/// Additionally, the API is a bit less cumbersome than manual casting: | |
/// * [JsonArray] | |
/// * [JsonObject] | |
library json; | |
import 'dart:convert'; | |
// ignore_for_file: cast_nullable_to_non_nullable | |
// When we do have a failure, the JSON should be easy to read. | |
const _prettyJson = JsonEncoder.withIndent(' '); | |
/// Extensions on `List<Object?>`, which could be a decoded JSON Array. | |
/// | |
/// ``` | |
/// // Standard method. | |
/// json.map((n) => SomethingElse.fromJson(n as Map<String, Object?>)).toList() | |
/// | |
/// // Using this library. | |
/// json.readMappedObjects(SomethingeElse.fromJson) | |
/// ``` | |
extension JsonArray on List<Object?> { | |
/// Returns a list of every element of the array, decoded from JSON. | |
List<T> readMappedObjects<T>(T Function(Map<String, Object?>) forEach) { | |
return map((element) { | |
if (element is Map<String, Object?>) { | |
return forEach(element); | |
} | |
throw FormatException( | |
'Expected nested JSON object, got ${element.runtimeType}', | |
); | |
}).toList(); | |
} | |
} | |
/// Extensions on `Map<String, Object?>`, which could be a decoded JSON Object. | |
/// | |
/// ``` | |
/// // Standard method. | |
/// json['foo'] as Map<String, Object?> | |
/// | |
/// // Using this library. | |
/// json.readObject('foo') | |
/// ``` | |
extension JsonObject on Map<String, Object?> { | |
Never _throwFormatException(String key, Object? value, String expected) { | |
final source = _prettyJson.convert(this); | |
throw FormatException( | |
'"$key" expected $expected, got ${value.runtimeType}', | |
source, | |
source.indexOf(RegExp('\b"$key"\b:')), | |
); | |
} | |
T _readHelper<T>(String key) { | |
final value = this[key]; | |
if (value is T) { | |
return value; | |
} | |
_throwFormatException(key, value, '$T'); | |
} | |
/// Returns value of [key] as a sub-type of [num], otherwise throws. | |
T readNumber<T extends num>(String key) => _readHelper(key); | |
/// Returns value of [key] as a sub-type of [num] or `null`, otherwise throws. | |
T? readNumberOrNull<T extends num>(String key) => _readHelper(key); | |
/// Returns value of [key] as a [String], otherwise throws. | |
String readString(String key) => _readHelper(key); | |
/// Returns value of [key] as a [String] or `null`, otherwise throws. | |
String? readStringOrNull(String key) => _readHelper(key); | |
/// Returns value of [key] as a [JsonObject], otherwise throws. | |
Map<String, Object?> readObject(String key) => _readHelper(key); | |
/// Returns value of [key] as a [JsonObject] or `null`, otherwise throws. | |
Map<String, Object?>? readObjectOrNull(String key) => _readHelper(key); | |
/// Returns value of [key] as a [JsonArray], otherwise throws. | |
List<Object?> readArray(String key) => _readHelper(key); | |
/// Returns value of [key] as a [JsonArray], otherwise throws. | |
/// | |
/// If the value was `null`, an empty array is returned instead. | |
List<Object?> readArrayOrEmpty(String key) => _readHelper(key) ?? []; | |
/// Returns value of [key] as a [JsonArray] or `null`, otherwise throws. | |
List<Object?>? readArrayOrNull(String key) => _readHelper(key); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Here is an example of the errors you might get: