JSON Safe
Safe JSON decoding & deserialization, convenient types, and pretty encoding.
JsonMap
Convenient type alias for JSON objects.
typedef JsonMap = Map<String, Object?>;
Improves readability by making JSON object semantics explicit. It does not introduce runtime checks, enforce constraints, or change the underlying type.
Tip
Projects commonly define this in internal code (example).
This removes the need to add this typedef in each new application or API client.
jsonEncode
Uses Dart's jsonEncode but requires the input to be a JsonMap
instead of allowing any Object?.
import 'dart:convert';
/// Type-safe wrapper over [jsonEncode] that accepts a `Map` instead of `Object?`
String jsonEncode(JsonMap map) => jsonEncode(map);
jsonEncodePretty
Similar to jsonEncode but produces pretty-printed JSON.
String jsonEncodePretty(JsonMap map) =>
convert.JsonEncoder.withIndent(' ' * 2).convert(map);
Uses a hardcoded, opinionated indentation of 2 spaces, matching the convention used by many modern frameworks and tools.
Commonly used when saving a JSON config file.
deserializeJson
Deserializes a JSON string into a data class.
const input = '{"email": "contact@example.com", "userId": 2}';
final result = deserializeJson(input, User.fromJson);
Throws a JsonParseException if the JSON:
- is malformed.
- is valid but its top-level value is not a JSON object.
- e.g., a top-level List. Most REST APIs return a JSON object rather than a top-level list, making it easier to add new properties without breaking existing clients.
- does not match the expected model.
- Cast/null-assert errors inside fromJson are treated as expected input validation errors.
You may also refer to JsonParseException subclasses to handle a specific case.
See also:
decodeJsonStringToMapdeserializeJsonMap
What about safe_json package?
safe_json's approach
provides stricter validation and more explicit errors, but is not compatible with fromJson models generated by or written in the style of json_serializable, which commonly rely on casts and null assertions:
@immutable
class User {
const User({required this.email, required this.userId});
factory User.fromJson(JsonMap json) =>
User(email: json['email']! as String, userId: json['userId']! as int);
final String email;
final int userId;
}
json_safe instead catches TypeError thrown by the supplied fromJson function and rethrows it as JsonDeserializationException.
Although TypeError normally indicates a programming error, treating it as a deserialization failure is a practical compromise because this pattern is the standard for handwritten and generated fromJson implementations.
It also ensures fromJson methods/constructors are independent of any specific library (including json_safe).