json_son 0.5.0
json_son: ^0.5.0 copied to clipboard
A Dart utility package providing helper functions to flexibly parse JSON values that might have inconsistent data types (e.g., strings to numbers, strings/numbers to booleans).
json_son (Just Straightens Out Nonsense) #
A Dart utility package providing helper functions to flexibly parse JSON values that might have inconsistent data types. This is especially useful when dealing with APIs that might send numbers as strings, booleans as strings/integers, dates in various formats, single items instead of lists, comma-separated string lists, or strings with leading/trailing whitespace.
This package offers two approaches:
-
Functional Approach: Helper functions designed to be used with JSON deserialization, often in conjunction with code generation libraries like
json_serializable(used byfreezed), by annotating DTO fields with@JsonKey(fromJson: ...). -
Class-Based Approach: A fluent
JsonSonclass that wraps a JSON object and provides methods for type-safe access, making it easier to work with complex nested structures.
Features #
Core Features #
- Convert dynamic JSON values to
int?,double?,num?,bool?,String?,DateTime?,Uri?,List<T>?, orList<T>. - Parse comma-separated strings into
List<String>?. - Normalize strings (trimming, case conversion).
- Gracefully handles
nulls, empty strings, and parsing failures by returningnull(or an empty list for non-null list helpers). - Supports common alternative representations (e.g., "true" or 1 for boolean
true, numeric strings for numbers, ISO 8601 or timestamps forDateTime). - Handles APIs that may return a single item where a list is expected.
- NEW in 0.5.0: Parse enums, BigInt, Duration, phone numbers, slugs, and currency values.
Enhanced JsonSon Class #
- Error Tracking: Detailed error tracking with path information for debugging and validation.
- Advanced Constructors: Multiple constructors for different data sources:
fromApiResponse: Handle API responses that might be strings or mapsfromMapSafe: Safely create JsonSon instances with error handling
- Path-Based Access: Access nested values using dot notation (e.g.,
user.address.city) - Batch Operations: Process multiple keys or paths at once
- Fallback Keys: Try multiple keys in sequence until a value is found
- Transformation Methods: Transform JSON data with mapping functions
- Validation: Check for required keys and paths with detailed error messages
- NEW in 0.5.0: Deep merge, diff, pick, flatten/unflatten, conditional getters, query string conversion
Extensions #
- Map Extensions: Direct access to JsonSon methods from Map objects
- API Extensions: Support for common API patterns including:
- Pagination information extraction
- Error handling with code and message extraction
- User information normalization
- Timestamp handling
Validation Framework #
- Fluent Validation API: Chain validation rules for complex validation logic
- Type Validation: Validate types (string, integer, boolean, etc.)
- Format Validation: Validate formats (email, URL, patterns)
- Range Validation: Validate numeric ranges and string lengths
- Custom Validation: Create custom validation rules
- Nested Validation: Validate nested objects and array items
- NEW in 0.5.0: Phone, UUID, credit card, date range, conditional validation, array uniqueness
Getting Started #
Installation #
Add this to your package's pubspec.yaml file:
dependencies:
json_son: ^0.5.0 # Or the latest version
Then, run flutter pub get or dart pub get.
Usage #
Functional Approach
Import the library and use the helper functions in your DTOs:
import 'package:json_son/json_son.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
part 'my_model.freezed.dart';
part 'my_model.g.dart';
@freezed
abstract class MyModel with _$MyModel {
const factory MyModel({
@JsonKey(fromJson: flexibleIntFromJson) int? anInt,
@JsonKey(fromJson: flexibleDoubleFromJson) double? aDouble,
@JsonKey(fromJson: flexibleNumFromJson) num? aNum,
@JsonKey(fromJson: flexibleBoolFromJson) bool? aBool,
@JsonKey(fromJson: flexibleStringFromJson) String? aString,
@JsonKey(fromJson: flexibleDateTimeFromJson) DateTime? aDateTime,
@JsonKey(fromJson: flexibleUriFromJson) Uri? aUri,
@JsonKey(fromJson: (json) => flexibleListFromJson<int>(json, flexibleIntFromJson)) List<int?>? nullableIntList,
@JsonKey(fromJson: (json) => flexibleListNotNullFromJson<String>(json, flexibleStringFromJson)) List<String> nonNullStringList,
@JsonKey(fromJson: flexibleCommaSeparatedListFromJson) List<String>? tags,
@JsonKey(fromJson: flexibleTrimmedStringFromJson) String? description,
@JsonKey(fromJson: flexibleLowerStringFromJson) String? category,
@JsonKey(fromJson: flexibleUpperStringFromJson) String? productCode,
String? normallyAString, // No helper needed if type is consistent
}) = _MyModel;
factory MyModel.fromJson(Map<String, dynamic> json) => _$MyModelFromJson(json);
}
Make sure to run your build runner after adding these annotations:
flutter pub run build_runner build --delete-conflicting-outputs
Class-Based Approach
Use the enhanced JsonSon class for a more fluent API with advanced features:
import 'package:json_son/json_son.dart';
// Parse JSON data with error tracking
final Map<String, dynamic> jsonData = getJsonFromSomewhere();
final json = JsonSon(jsonData);
// Basic property access with type conversion
final id = json.getInt('id');
final name = json.getString('name');
final isActive = json.getBool('isActive');
// With default values
final count = json.getIntOrDefault('count', 0);
final status = json.getStringOrDefault('status', 'pending');
// Access nested objects
final user = json.getObject('user');
if (user != null) {
final firstName = user.getString('firstName');
final lastName = user.getString('lastName');
// Access nested objects within nested objects
final address = user.getObject('address');
if (address != null) {
final city = address.getString('city');
final zipCode = address.getString('zipCode');
}
}
// Path-based access with dot notation
final city = json.getStringPath('user.address.city');
final zipCode = json.getStringPath('user.address.zipCode');
final isVerified = json.getBoolPath('user.account.isVerified');
// List handling
final scores = json.getList<int>('scores', flexibleIntFromJson);
// Error tracking
if (json.hasErrors) {
print('Errors occurred: ${json.errors}');
}
// Advanced constructors
final apiJson = JsonSon.fromApiResponse(responseBody);
final safeJson = JsonSon.fromMapSafe(potentiallyInvalidMap);
// Batch operations
final userInfo = json.getBatch(['id', 'name', 'email']);
final addressInfo = json.getPathBatch(['user.address.street', 'user.address.city']);
// Fallback keys (try multiple keys in sequence)
final userId = json.getIntWithFallbacks(['id', 'userId', 'user_id']);
final userName = json.getStringWithFallbacks(['name', 'username', 'displayName']);
// Required fields validation
final requiredFields = ['id', 'name', 'email'];
if (!json.hasRequiredKeys(requiredFields)) {
print('Missing required fields: ${json.getMissingKeys(requiredFields)}');
}
// Transform data
final transformedJson = json.transform((key, value) {
if (key == 'dates') return value is List ? value.map((d) => DateTime.parse(d)).toList() : null;
return value;
});
Extensions
Use the Map extensions for quick access to JsonSon methods:
import 'package:json_son/json_son.dart';
final Map<String, dynamic> userData = {'id': '123', 'name': 'John', 'active': 'true'};
// Direct access to JsonSon methods from Map
final id = userData.getInt('id'); // 123
final name = userData.getString('name'); // 'John'
final isActive = userData.getBool('active'); // true
// Convert to JsonSon instance
final json = userData.json;
API Pattern Extensions
Use specialized extensions for common API patterns:
import 'package:json_son/json_son.dart';
// Pagination information
final paginationJson = JsonSon({
'total': 100,
'page': 2,
'per_page': 25,
'has_more': true
});
final pagination = paginationJson.getPaginationInfo();
print('Page ${pagination.page} of ${pagination.totalPages}');
print('Has next page: ${pagination.hasNextPage}');
// API error handling
final errorJson = JsonSon({
'error': {
'code': 'AUTH_FAILED',
'message': 'Authentication failed'
}
});
final error = errorJson.getApiError();
if (error != null) {
print('Error: ${error.userMessage}');
if (error.hasErrorCode('AUTH_FAILED')) {
// Handle authentication error
}
}
// User information
final userJson = JsonSon({
'id': 1001,
'first_name': 'Jane',
'last_name': 'Doe',
'email': 'jane.doe@example.com'
});
final user = userJson.getUserInfo();
print('Full name: ${user.fullName}'); // 'Jane Doe'
New Type Parsers (v0.5.0)
Parse enums, durations, BigInt, and more:
import 'package:json_son/json_son.dart';
enum Status { pending, active, completed }
// Enum parsing (case-insensitive string or index)
final status = flexibleEnumFromJson('active', Status.values); // Status.active
final statusByIndex = flexibleEnumFromJson(1, Status.values); // Status.active
final statusWithFallback = flexibleEnumFromJson('invalid', Status.values, fallback: Status.pending);
// BigInt for large numbers
final bigNum = flexibleBigIntFromJson('12345678901234567890');
// Duration parsing (ISO 8601, human-readable, or map)
final duration1 = flexibleDurationFromJson('PT1H30M'); // 1 hour 30 minutes
final duration2 = flexibleDurationFromJson('2h 30m'); // 2 hours 30 minutes
final duration3 = flexibleDurationFromJson({'hours': 2, 'minutes': 30});
final duration4 = flexibleDurationFromJson(5000); // 5000 milliseconds
// Phone number normalization
final phone = flexiblePhoneFromJson('(555) 123-4567'); // '5551234567'
final intlPhone = flexiblePhoneFromJson('+1 555 123 4567'); // '+15551234567'
// URL-safe slug generation
final slug = flexibleSlugFromJson('Hello World! 123'); // 'hello-world-123'
// Currency parsing
final price = flexibleCurrencyFromJson('\$1,234.56'); // CurrencyValue(1234.56, 'USD')
final euroPrice = flexibleCurrencyFromJson('€100'); // CurrencyValue(100.0, 'EUR')
final priceFromMap = flexibleCurrencyFromJson({'amount': 50.0, 'currency': 'GBP'});
Advanced JsonSon Operations (v0.5.0)
Use the new advanced operations for complex data manipulation:
import 'package:json_son/json_son.dart';
final json = JsonSon({
'user': {
'name': 'John',
'settings': {'theme': 'dark', 'notifications': true}
},
'scores': [85, 92, 78]
});
// New type getters
final duration = json.getDuration('timeout');
final bigId = json.getBigInt('largeId');
final price = json.getCurrency('price');
final status = json.getEnum('status', Status.values);
final phone = json.getPhone('phone');
final slug = json.getSlug('title');
// Path-based list access
final scores = json.getListPath<int>('data.scores', flexibleIntFromJson);
// Deep merge (recursively merges nested objects)
final base = JsonSon({'a': 1, 'nested': {'b': 2, 'c': 3}});
final override = JsonSon({'nested': {'c': 30, 'd': 4}});
final merged = base.deepMerge(override);
// Result: {'a': 1, 'nested': {'b': 2, 'c': 30, 'd': 4}}
// Diff (compare two JsonSon objects)
final original = JsonSon({'a': 1, 'b': 2});
final modified = JsonSon({'a': 1, 'b': 20, 'c': 3});
final diff = original.diff(modified);
// diff['added'] = {'c': 3}
// diff['changed'] = {'b': {'from': 2, 'to': 20}}
// Pick (select nested paths)
final picked = json.pick(['user.name', 'user.settings.theme']);
// Result: {'user': {'name': 'John', 'settings': {'theme': 'dark'}}}
// Flatten/Unflatten
final flat = json.flatten(); // {'user.name': 'John', 'user.settings.theme': 'dark', ...}
final nested = JsonSon.unflatten({'a.b.c': 1}); // {'a': {'b': {'c': 1}}}
// Conditional getters
final age = json.getIntIf('age', (v) => v >= 18); // Only returns if >= 18
final name = json.getStringIf('name', (v) => v.length >= 3);
// Convert to query string
final queryJson = JsonSon({'page': 1, 'search': 'hello world'});
final queryString = queryJson.toQueryString(); // 'page=1&search=hello%20world'
Enhanced Validation (v0.5.0)
New validation methods for common patterns:
import 'package:json_son/json_son.dart';
final json = JsonSon({
'phone': '555-123-4567',
'uuid': '550e8400-e29b-41d4-a716-446655440000',
'card': '4532015112830366',
'birthDate': '1990-05-15',
'type': 'business',
'company': 'Acme Inc',
'tags': ['a', 'b', 'c'],
'password': 'secret123',
'confirmPassword': 'secret123',
'url': 'https://example.com/page'
});
final validator = JsonSonValidator(json)
// Format validators
..phone('phone')
..uuid('uuid')
..creditCard('card')
// Date validators
..dateRange('birthDate', min: DateTime(1900), max: DateTime.now())
..pastDate('birthDate')
// Conditional validation
..requiredWhen('company', 'type', 'business') // company required when type is 'business'
..requiredWith('confirmPassword', 'password') // confirmPassword required when password exists
// Array validators
..unique('tags') // All items must be unique
..minItems('tags', 1)
..maxItems('tags', 10)
// Comparison validators
..between('age', 0, 120)
..equals('confirmPassword', 'password') // Must match password field
..different('newPassword', 'oldPassword')
// String validators
..contains('email', '@')
..startsWith('url', 'https://')
..endsWith('file', '.pdf');
if (!validator.isValid) {
print('Errors: ${validator.errors}');
}
Validation Framework
Use the fluent validation API for complex validations:
import 'package:json_son/json_son.dart';
final json = JsonSon({
'username': 'johndoe',
'email': 'john@example.com',
'age': 25,
'password': 'pass123',
'role': 'user',
'address': {
'city': 'New York',
'zipcode': '10001'
}
});
final validator = JsonSonValidator(json)
// Required fields
..required('username')
..required('email')
..required('password')
// Type validation
..string('username')
..integer('age')
// Format validation
..email('email')
..pattern('zipcode', RegExp(r'^\d{5}(-\d{4})?$'))
// Range validation
..min('age', 18)
..minLength('password', 8)
..maxLength('username', 20)
// Allowed values
..oneOf('role', ['user', 'admin', 'moderator'])
// Nested validation
..nested('address', (addressValidator) {
addressValidator
..required('city')
..required('zipcode');
});
if (!validator.isValid) {
print('Validation errors:');
validator.errors.forEach((field, error) {
print('$field: $error');
});
}
Available Classes and APIs #
JsonSon Class #
The JsonSon class is the core of the class-based approach, providing a fluent API for working with JSON data:
class JsonSon {
// Constructors
JsonSon(Map<String, dynamic> data);
JsonSon.empty();
factory JsonSon.fromApiResponse(dynamic response);
factory JsonSon.fromMapSafe(dynamic map);
// Basic getters
Map<String, dynamic> get rawData;
bool get hasErrors;
List<String> get errors;
// Type-safe access methods
int? getInt(String key);
double? getDouble(String key);
num? getNum(String key);
bool? getBool(String key);
String? getString(String key);
DateTime? getDateTime(String key);
Uri? getUri(String key);
JsonSon? getObject(String key);
List<T>? getList<T>(String key, T? Function(dynamic) itemParser);
// With default values
int getIntOrDefault(String key, int defaultValue);
double getDoubleOrDefault(String key, double defaultValue);
bool getBoolOrDefault(String key, bool defaultValue);
String getStringOrDefault(String key, String defaultValue);
// Path-based access
dynamic getPath(String path);
int? getIntPath(String path);
double? getDoublePath(String path);
bool? getBoolPath(String path);
String? getStringPath(String path);
DateTime? getDateTimePath(String path);
Uri? getUriPath(String path);
JsonSon? getObjectPath(String path);
// Batch operations
Map<String, dynamic> getBatch(List<String> keys);
Map<String, dynamic> getPathBatch(List<String> paths);
// Fallback keys
T? getWithFallbacks<T>(List<String> keys, T? Function(dynamic) getter);
int? getIntWithFallbacks(List<String> keys);
double? getDoubleWithFallbacks(List<String> keys);
bool? getBoolWithFallbacks(List<String> keys);
String? getStringWithFallbacks(List<String> keys);
// Validation
bool hasKey(String key);
bool hasPath(String path);
bool hasRequiredKeys(List<String> keys);
bool hasRequiredPaths(List<String> paths);
List<String> getMissingKeys(List<String> keys);
List<String> getMissingPaths(List<String> paths);
// Transformation
JsonSon transform(dynamic Function(String key, dynamic value) transformer);
JsonSon merge(JsonSon other, {bool overwrite = true});
}
JsonSonValidator #
The JsonSonValidator class provides a fluent API for validating JSON data:
class JsonSonValidator {
JsonSonValidator(JsonSon json);
Map<String, String> get errors;
bool get isValid;
// Basic validation
JsonSonValidator required(String key, {String? message});
JsonSonValidator requiredPath(String path, {String? message});
// Type validation
JsonSonValidator string(String key, {String? message});
JsonSonValidator integer(String key, {String? message});
JsonSonValidator double(String key, {String? message});
JsonSonValidator boolean(String key, {String? message});
JsonSonValidator date(String key, {String? message});
JsonSonValidator array(String key, {String? message});
JsonSonValidator object(String key, {String? message});
// Format validation
JsonSonValidator email(String key, {String? message});
JsonSonValidator url(String key, {String? message});
JsonSonValidator pattern(String key, RegExp pattern, {String? message});
// Range validation
JsonSonValidator min(String key, num minValue, {String? message});
JsonSonValidator max(String key, num maxValue, {String? message});
JsonSonValidator minLength(String key, int minLength, {String? message});
JsonSonValidator maxLength(String key, int maxLength, {String? message});
// Value validation
JsonSonValidator oneOf(String key, List<dynamic> allowedValues, {String? message});
JsonSonValidator custom(String key, bool Function(dynamic) validator, {String? message});
// Nested validation
JsonSonValidator nested(String key, void Function(JsonSonValidator) validator);
JsonSonValidator eachItem(String key, void Function(JsonSonValidator, int) itemValidator);
// New in v0.5.0
// Format validators
JsonSonValidator phone(String key, {String? message, int? minDigits, int? maxDigits});
JsonSonValidator uuid(String key, {String? message});
JsonSonValidator creditCard(String key, {String? message});
// Date validators
JsonSonValidator dateRange(String key, {DateTime? min, DateTime? max, String? message});
JsonSonValidator pastDate(String key, {String? message});
JsonSonValidator futureDate(String key, {String? message});
// Conditional validators
JsonSonValidator when(String conditionKey, bool Function(dynamic) condition, void Function(JsonSonValidator) validatorFn);
JsonSonValidator requiredWhen(String key, String conditionKey, dynamic conditionValue, {String? message});
JsonSonValidator requiredWith(String key, String otherKey, {String? message});
JsonSonValidator requiredWithout(String key, String otherKey, {String? message});
// Array validators
JsonSonValidator unique(String key, {String? message, dynamic Function(dynamic)? by});
JsonSonValidator minItems(String key, int count, {String? message});
JsonSonValidator maxItems(String key, int count, {String? message});
// Comparison validators
JsonSonValidator between(String key, num minValue, num maxValue, {String? message});
JsonSonValidator equals(String key, String otherKey, {String? message});
JsonSonValidator different(String key, String otherKey, {String? message});
// String validators
JsonSonValidator contains(String key, String substring, {String? message, bool caseSensitive});
JsonSonValidator startsWith(String key, String prefix, {String? message});
JsonSonValidator endsWith(String key, String suffix, {String? message});
}
Helper Classes #
// Currency value helper (new in v0.5.0)
class CurrencyValue {
final double amount;
final String? currencyCode;
String toString() => currencyCode != null ? '$currencyCode ${amount.toStringAsFixed(2)}' : amount.toStringAsFixed(2);
}
// Pagination information helper
class PaginationInfo {
final int? total;
final int? page;
final int? limit;
final bool? hasMore;
final int? nextPage;
int? get totalPages => total != null && limit != null ? (total / limit).ceil() : null;
bool get hasNextPage => hasMore == true || (nextPage != null) || (page != null && totalPages != null && page < totalPages);
}
// API error helper
class ApiError {
final String? message;
final String? code;
final String? details;
final List<String>? errors;
String get userMessage => message ?? (errors?.isNotEmpty == true ? errors!.first : 'An unknown error occurred');
bool hasErrorCode(String errorCode) => code == errorCode;
}
// User information helper
class UserInfo {
final int? id;
final String? name;
final String? email;
final String? firstName;
final String? lastName;
final String? avatar;
String? get fullName => firstName != null && lastName != null ? '$firstName $lastName' : name;
}
// Timestamp information helper
class TimestampInfo {
final DateTime? createdAt;
final DateTime? updatedAt;
final DateTime? deletedAt;
bool get isDeleted => deletedAt != null;
Duration? get age => createdAt != null ? DateTime.now().difference(createdAt!) : null;
Duration? get timeSinceUpdate => updatedAt != null ? DateTime.now().difference(updatedAt!) : null;
}
Functional API #
-
int? flexibleIntFromJson(dynamic value)- Parses to
int?. Handlesnull,int,double(truncates),String. - Empty or unparseable string results in
null.
- Parses to
-
double? flexibleDoubleFromJson(dynamic value)- Parses to
double?. Handlesnull,double,int,String. - Empty or unparseable string results in
null.
- Parses to
-
num? flexibleNumFromJson(dynamic value)- Parses to
num?(can beintordouble). Handlesnull,num,String. - Empty or unparseable string results in
null.
- Parses to
-
bool? flexibleBoolFromJson(dynamic value)- Parses to
bool?. Handlesnull,bool,String("true", "false", "1", "0", case-insensitive),int(1, 0). - Empty string or unhandled values result in
null.
- Parses to
-
String? flexibleStringFromJson(dynamic value)- Parses to
String?. Handlesnull,String. Converts other types (e.g.,int,bool) to string via.toString().
- Parses to
-
DateTime? flexibleDateTimeFromJson(dynamic value)- Parses to
DateTime?(UTC). Handlesnull. - If
int: assumes milliseconds since epoch. - If
String: tries ISO 8601, then numeric string as milliseconds since epoch. - Empty or unparseable string results in
null.
- Parses to
-
Uri? flexibleUriFromJson(dynamic value)- Parses to
Uri?. Handlesnull,String. - Empty or unparseable string results in
null.
- Parses to
-
List<T?>? flexibleListFromJson<T>(dynamic value, T? Function(dynamic) itemParser)- Ensures result is a
List<T?>?. Handlesnullinput, or a single item being returned by API instead of a list. - Parses each item in the list using the provided
itemParser. - Filters out items that parse to
null. - Returns
nullif the input isnull, or if a single item input parses tonull.
- Ensures result is a
-
List<T> flexibleListNotNullFromJson<T>(dynamic value, T? Function(dynamic) itemParser)- Ensures result is a non-nullable
List<T>. Handlesnullinput or a single item. - Parses each item in the list using the provided
itemParser. - Filters out items that parse to
null. - Returns an empty list if the input is
nullor if all items parse tonull.
- Ensures result is a non-nullable
-
int flexibleRequiredIntFromJson(dynamic value)- Non-nullable version of
flexibleIntFromJson. - Returns
0instead ofnullwhen the input isnullor cannot be parsed as an integer. - Useful when you need to guarantee a non-null integer value.
- Non-nullable version of
-
double flexibleRequiredDoubleFromJson(dynamic value)- Non-nullable version of
flexibleDoubleFromJson. - Returns
0.0instead ofnullwhen the input isnullor cannot be parsed as a double. - Useful when you need to guarantee a non-null double value.
- Non-nullable version of
-
String flexibleRequiredStringFromJson(dynamic value)- Non-nullable version of
flexibleStringFromJson. - Returns an empty string (
"") instead ofnullwhen the input isnull. - Useful when you need to guarantee a non-null string value.
- Non-nullable version of
-
bool flexibleRequiredBoolFromJson(dynamic value)- Non-nullable version of
flexibleBoolFromJson. - Returns
falseinstead ofnullwhen the input isnullor cannot be parsed as a boolean. - Useful when you need to guarantee a non-null boolean value.
- Non-nullable version of
-
List<T> flexibleRequiredListFromJson<T>(dynamic value, T? Function(dynamic) itemParser)- Alias for
flexibleListNotNullFromJsonto provide a consistent naming convention. - Returns an empty list if the input is
nullor if all items parse tonull. - Useful when you need to guarantee a non-null list.
- Alias for
-
List<String>? flexibleCommaSeparatedListFromJson(dynamic value)- Parses comma-separated strings (e.g., "apple, banana, cherry") into
List<String>?. - Trims whitespace from each item and filters out empty strings.
- Handles
nullinput. If input is already a list, its elements are stringified and trimmed.
- Parses comma-separated strings (e.g., "apple, banana, cherry") into
-
String? flexibleTrimmedStringFromJson(dynamic value)- Converts input to string, trims whitespace. Returns
nullif the result is empty, otherwise the trimmed string.
- Converts input to string, trims whitespace. Returns
-
String? flexibleLowerStringFromJson(dynamic value)- Converts input to string, trims it, then converts to lowercase. Returns
nullif the trimmed string is empty.
- Converts input to string, trims it, then converts to lowercase. Returns
-
String? flexibleUpperStringFromJson(dynamic value)- Converts input to string, trims it, then converts to uppercase. Returns
nullif the trimmed string is empty.
- Converts input to string, trims it, then converts to uppercase. Returns
-
Map<K, V>? flexibleMapFromJson<K, V>(dynamic value, MapEntry<K, V>? Function(dynamic key, dynamic value) mapper)- Transforms a map by applying a transformation function to each key-value pair.
- Handles
nullinput by returningnull. - The
mapperfunction can returnnullto skip entries. - Example: Converts
{"a": "1", "b": "2"}to{"a": 1, "b": 2}when used withint.tryParse.
-
Map<K, V> flexibleMapNotNullFromJson<K, V>(dynamic value, MapEntry<K, V>? Function(dynamic key, dynamic value) mapper)- Similar to
flexibleMapFromJsonbut returns an empty map instead ofnullfornullinput. - The
mapperfunction can returnnullto skip entries. - Example: Converts
{"a": "1", "b": "x"}to{"a": 1}when used withint.tryParse.
- Similar to
New in v0.5.0
-
T? flexibleEnumFromJson<T extends Enum>(dynamic value, List<T> values, {T? fallback})- Parses to an enum value. Handles
String(case-insensitive name matching) andint(index). - Returns
fallbackif parsing fails or value is null.
- Parses to an enum value. Handles
-
T flexibleRequiredEnumFromJson<T extends Enum>(dynamic value, List<T> values, T fallback)- Non-nullable version that always returns the fallback if parsing fails.
-
BigInt? flexibleBigIntFromJson(dynamic value)- Parses to
BigInt?. Handlesnull,int,String, andBigInt. - Useful for large integers that overflow int64.
- Parses to
-
Duration? flexibleDurationFromJson(dynamic value)- Parses to
Duration?. Handles:intas milliseconds- ISO 8601 format (e.g., "PT1H30M", "P1D")
- Human-readable format (e.g., "1h 30m", "2d 5h", "90s", "500ms")
Mapwith keys like "hours", "minutes", "seconds"
- Parses to
-
String? flexiblePhoneFromJson(dynamic value)- Normalizes phone numbers by stripping non-digit characters (preserves leading +).
- Example: "(555) 123-4567" → "5551234567"
-
String? flexibleSlugFromJson(dynamic value)- Converts strings to URL-safe slugs.
- Example: "Hello World! 123" → "hello-world-123"
-
CurrencyValue? flexibleCurrencyFromJson(dynamic value)- Parses currency values from various formats.
- Handles: "$1,234.56", "100.00 USD", "€100",
Mapwith amount/currency. - Returns
CurrencyValuewithamountand optionalcurrencyCode.
Class-Based API #
The JsonSon class provides a more fluent interface for working with JSON data:
Constructors
JsonSon(Map<String, dynamic> data)- Creates a new instance from a MapJsonSon.fromMap(Map<String, dynamic> map)- Static constructor from a MapJsonSon.fromJson(String json)- Static constructor from a JSON string
Basic Type Getters
getInt(String key)- Gets an int valuegetString(String key)- Gets a String valuegetDouble(String key)- Gets a double valuegetBool(String key)- Gets a bool valuegetDateTime(String key)- Gets a DateTime valuegetNum(String key)- Gets a num valuegetUri(String key)- Gets a Uri value
With Default Values
getIntOrDefault(String key, int defaultValue)- Gets an int with a default valuegetStringOrDefault(String key, String defaultValue)- Gets a String with a default valuegetDoubleOrDefault(String key, double defaultValue)- Gets a double with a default valuegetBoolOrDefault(String key, bool defaultValue)- Gets a bool with a default valuegetNumOrDefault(String key, num defaultValue)- Gets a num with a default value
String Normalization
getTrimmedString(String key)- Gets a trimmed StringgetLowerString(String key)- Gets a lowercase StringgetUpperString(String key)- Gets an uppercase String
Nested Objects
getObject(String key)- Gets a nested JsonSon objectgetObjectList(String key)- Gets a List of JsonSon objects
Lists
getList<T>(String key, T? Function(dynamic) converter)- Gets a List of valuesgetListOrEmpty<T>(String key, T? Function(dynamic) converter)- Gets a non-null List of valuesgetCommaSeparatedList(String key)- Gets a List of Strings from a comma-separated string
Path-Based Access
getPath(String path)- Gets a value using dot notationgetIntPath(String path)- Gets an int using dot notationgetStringPath(String path)- Gets a String using dot notationgetDoublePath(String path)- Gets a double using dot notationgetBoolPath(String path)- Gets a bool using dot notationgetDateTimePath(String path)- Gets a DateTime using dot notationgetNumPath(String path)- Gets a num using dot notationgetUriPath(String path)- Gets a Uri using dot notationgetObjectPath(String path)- Gets a nested JsonSon object using dot notationgetDurationPath(String path)- Gets a Duration using dot notationgetBigIntPath(String path)- Gets a BigInt using dot notationgetListPath<T>(String path, T? Function(dynamic) converter)- Gets a List at a pathgetListPathOrEmpty<T>(String path, T? Function(dynamic) converter)- Gets a non-null List at a path
New Type Getters (v0.5.0)
getDuration(String key)- Gets a Duration valuegetBigInt(String key)- Gets a BigInt valuegetCurrency(String key)- Gets a CurrencyValuegetEnum<T>(String key, List<T> values, {T? fallback})- Gets an enum valuegetPhone(String key)- Gets a normalized phone numbergetSlug(String key)- Gets a URL-safe slug
Advanced Operations (v0.5.0)
deepMerge(JsonSon other)- Recursively merges nested objectsdiff(JsonSon other)- Compares and returns added/removed/changed keyspick(List<String> paths)- Selects values at nested pathsflatten({String separator})- Converts nested objects to dot-notation keysunflatten(Map<String, dynamic> flatMap, {String separator})- Converts dot-notation to nestedgetIf<T>(String key, T? Function(String) getter, bool Function(dynamic) condition)- Conditional gettergetIntIf(String key, bool Function(int) condition)- Gets int only if condition metgetStringIf(String key, bool Function(String) condition)- Gets string only if condition mettoQueryString({bool encode})- Converts to URL query string
Utility Methods
hasKey(String key)- Checks if a key existskeys- Gets all keys in the underlying maprawData- Gets the raw underlying mapselect(List<String> keys)- Returns a new JsonSon with only the specified keysexclude(List<String> keys)- Returns a new JsonSon without the specified keystoJson()- Converts to a JSON string
Contributing #
Feel free to open an issue or submit a pull request if you have suggestions or find bugs.
Additional information #
- Source Code: You can find the source code on https://github.com/scalecode-solutions/json_son.
- Issue Tracker: If you encounter any bugs or have feature requests, please file an issue on our https://github.com/scalecode-solutions/json_son/issues.
- License: This package is licensed under the MIT License. See the
LICENSEfile for more details. - Contributions: We welcome contributions! Please feel free to submit a pull request or open an issue. We generally respond to issues and pull requests within a few business days, but response times may vary.
- Further Information: For more detailed information on specific functions and their behavior, please refer to the inline documentation within the source code.
MV❤️