mirror of
https://github.com/hanskokx/arcane_helper_utils.git
synced 2026-06-09 23:26:05 +02:00
v1.4.8: Add recursive JSON-safe serialization utilities
- Introduced extension methods for recursive serialization and deserialization of nested maps/lists: `toJsonValue()`, `fromJsonValue()`, `toJsonMap()`, `fromJsonMap()`, `toJsonList()`, and `fromJsonList()`. - Updated README.md to include new utilities. - Added tests for JSON value extensions. - Bumped version to 1.4.8. Signed-off-by: Hans Kokx <hans.d.kokx@gmail.com>
This commit is contained in:
@@ -1,3 +1,10 @@
|
||||
## 1.4.8
|
||||
|
||||
- Added extension methods for recursive JSON-safe serialization and deserialization of nested maps/lists:
|
||||
- `toJsonValue()` / `fromJsonValue()`
|
||||
- `toJsonMap()` / `fromJsonMap()`
|
||||
- `toJsonList()` / `fromJsonList()`
|
||||
|
||||
## 1.4.7
|
||||
|
||||
- Added the `isExpired` and `expiresSoon` getters to JWT tokens.
|
||||
|
||||
@@ -9,6 +9,7 @@ providing utility functions and extensions that simplify common tasks.
|
||||
|
||||
- **Ticker Utility**: A utility class that facilitates time-based actions, perfect for animations or any timing-related operations.
|
||||
- **JSON Converter**: Simplifies the process of converting JSON data into Dart objects.
|
||||
- **Recursive JSON Value Utilities**: Convert nested maps/lists into JSON-safe values and decode them back into typed nested structures.
|
||||
- **DateTime Extensions**: Adds additional functionality to the `DateTime` class, making it easier to format dates and calculate differences.
|
||||
- **String Extensions**: Enhances the `String` class by adding new methods for common transformations and checks.
|
||||
- **JWT Utilities**: Provides getters to parse a JWT token from a `String`, then get common properties from it.
|
||||
@@ -84,6 +85,37 @@ Here are some examples of how to use the utilities and extensions provided by th
|
||||
}
|
||||
```
|
||||
|
||||
### Recursive JSON Value Utilities
|
||||
|
||||
Use these extension methods to recursively normalize nested data before
|
||||
serializing it to JSON.
|
||||
|
||||
```dart
|
||||
final payload = {
|
||||
"metadata": {
|
||||
"tags": ["arcane", "framework"],
|
||||
},
|
||||
123: DateTime.utc(2026, 1, 1),
|
||||
};
|
||||
|
||||
final jsonSafe = payload.toJsonValue();
|
||||
// {
|
||||
// "metadata": {"tags": ["arcane", "framework"]},
|
||||
// "123": "2026-01-01 00:00:00.000Z"
|
||||
// }
|
||||
|
||||
final decoded = jsonSafe.fromJsonValue();
|
||||
|
||||
// Extension methods:
|
||||
final mapJson = payload.toJsonMap();
|
||||
final mapAgain = mapJson.fromJsonMap();
|
||||
|
||||
final nestedList = <Object?>[
|
||||
<Object?>[1, 2, <Object?, Object?>{"deep": true}],
|
||||
];
|
||||
final nestedListJson = nestedList.toJsonList();
|
||||
```
|
||||
|
||||
### DateTime Extensions
|
||||
|
||||
These extensions add helpful methods to the `DateTime` class, making it easier to handle common date and time operations such as formatting, comparisons, and calculations.
|
||||
|
||||
@@ -3,6 +3,7 @@ library arcane_helper_utils;
|
||||
export "package:arcane_helper_utils/src/classes/fixed_size_list.dart";
|
||||
export "package:arcane_helper_utils/src/extensions/date_time.dart";
|
||||
export "package:arcane_helper_utils/src/extensions/dynamic.dart";
|
||||
export "package:arcane_helper_utils/src/extensions/json_value.dart";
|
||||
export "package:arcane_helper_utils/src/extensions/jwt.dart";
|
||||
export "package:arcane_helper_utils/src/extensions/list.dart";
|
||||
export "package:arcane_helper_utils/src/extensions/string.dart";
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
/// Extension methods for recursively serializing and deserializing JSON-like
|
||||
/// values.
|
||||
extension JsonValueObjectExtension on Object? {
|
||||
/// Recursively serializes this value into a JSON-safe value.
|
||||
Object? toJsonValue() => _JsonValueHelper._toJsonValue(this);
|
||||
|
||||
/// Recursively deserializes this JSON-like value into nested Dart values.
|
||||
Object? fromJsonValue() => _JsonValueHelper._fromJsonValue(this);
|
||||
|
||||
/// Recursively deserializes this JSON-like value into a map.
|
||||
///
|
||||
/// Returns `null` when this value is `null` or not a map.
|
||||
Map<String, Object?>? fromJsonMap() => _JsonValueHelper._fromJsonMap(this);
|
||||
}
|
||||
|
||||
/// Extension methods for JSON map serialization.
|
||||
extension JsonValueMapExtension on Map<Object?, Object?> {
|
||||
/// Recursively serializes this map into a JSON-safe map with string keys.
|
||||
Map<String, Object?> toJsonMap() => _JsonValueHelper._toJsonMap(this);
|
||||
}
|
||||
|
||||
/// Extension methods for JSON list serialization and deserialization.
|
||||
extension JsonValueListExtension on List<Object?> {
|
||||
/// Recursively serializes this list into a JSON-safe list.
|
||||
List<Object?> toJsonList() => _JsonValueHelper._toJsonList(this);
|
||||
|
||||
/// Recursively deserializes this JSON-like list into nested Dart values.
|
||||
List<Object?> fromJsonList() => _JsonValueHelper._fromJsonList(this);
|
||||
}
|
||||
|
||||
abstract class _JsonValueHelper {
|
||||
static Object? _toJsonValue(Object? value) {
|
||||
if (value == null) return null;
|
||||
if (value is String || value is num || value is bool) return value;
|
||||
if (value is Map) return _toJsonMap(value);
|
||||
if (value is List) return _toJsonList(value.cast<Object?>());
|
||||
return value.toString();
|
||||
}
|
||||
|
||||
static Map<String, Object?> _toJsonMap(Map<Object?, Object?> map) {
|
||||
return map.map((k, v) => MapEntry(k.toString(), _toJsonValue(v)));
|
||||
}
|
||||
|
||||
static List<Object?> _toJsonList(List<Object?> list) {
|
||||
return list.map(_toJsonValue).toList();
|
||||
}
|
||||
|
||||
static Object? _fromJsonValue(Object? value) {
|
||||
if (value == null) return null;
|
||||
if (value is Map) return _fromJsonMap(value);
|
||||
if (value is List) return _fromJsonList(value.cast<Object?>());
|
||||
return value;
|
||||
}
|
||||
|
||||
static Map<String, Object?>? _fromJsonMap(Object? value) {
|
||||
if (value == null) return null;
|
||||
if (value is! Map) return null;
|
||||
return value.map((k, v) => MapEntry(k.toString(), _fromJsonValue(v)));
|
||||
}
|
||||
|
||||
static List<Object?> _fromJsonList(List<Object?> list) {
|
||||
return list.map(_fromJsonValue).toList();
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import "dart:convert";
|
||||
|
||||
typedef JwtPayload = Map<String, dynamic>;
|
||||
typedef JwtPayload = Map<String, Object?>;
|
||||
|
||||
/// An extension on `String` to extract useful information from JSON Web Tokens (JWT).
|
||||
///
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
name: arcane_helper_utils
|
||||
description: Provides a variety of helpful utilities and extensions for Flutter
|
||||
and Dart.
|
||||
version: 1.4.7
|
||||
version: 1.4.8
|
||||
repository: https://github.com/hanskokx/arcane_helper_utils
|
||||
issue_tracker: https://github.com/hanskokx/arcane_helper_utils/issues
|
||||
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
import "package:arcane_helper_utils/arcane_helper_utils.dart";
|
||||
import "package:test/test.dart";
|
||||
|
||||
void main() {
|
||||
group("JsonValue extensions", () {
|
||||
test("toJsonList serializes nested list values", () {
|
||||
final List<Object?> nested = <Object?>[
|
||||
<Object?>[
|
||||
1,
|
||||
2,
|
||||
<Object?, Object?>{"ok": true},
|
||||
],
|
||||
];
|
||||
|
||||
final result = nested.toJsonList();
|
||||
|
||||
expect(result, isA<List<Object?>>());
|
||||
expect((result.first as List<Object?>)[0], 1);
|
||||
expect(
|
||||
((result.first as List<Object?>)[2] as Map<String, Object?>)["ok"],
|
||||
isTrue,
|
||||
);
|
||||
});
|
||||
|
||||
test("toJsonMap serializes map keys and nested values", () {
|
||||
final map = <Object?, Object?>{
|
||||
123: "value",
|
||||
"nested": <Object?, Object?>{"a": 1},
|
||||
};
|
||||
|
||||
expect(map.toJsonMap().containsKey("123"), isTrue);
|
||||
expect(
|
||||
(map.toJsonMap()["nested"] as Map<String, Object?>)["a"],
|
||||
1,
|
||||
);
|
||||
});
|
||||
|
||||
test("fromJsonMap decodes nested structures from Object?", () {
|
||||
final Object value = <String, Object?>{
|
||||
"meta": <String, Object?>{
|
||||
"list": <Object?>[
|
||||
<String, Object?>{"x": 1},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
final decoded = value.fromJsonMap();
|
||||
final meta = decoded!["meta"]! as Map<String, Object?>;
|
||||
final list = meta["list"]! as List<Object?>;
|
||||
|
||||
expect((list.first as Map<String, Object?>)["x"], 1);
|
||||
});
|
||||
|
||||
test("toJsonValue and fromJsonValue round-trip primitives", () {
|
||||
const Object original = 42;
|
||||
final jsonValue = original.toJsonValue();
|
||||
final decoded = jsonValue.fromJsonValue();
|
||||
|
||||
expect(decoded, 42);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -48,4 +48,55 @@ void main() {
|
||||
expect(converter.toJson(null), null);
|
||||
});
|
||||
});
|
||||
|
||||
group("Json value extensions", () {
|
||||
test("toJsonValue recursively encodes nested maps and lists", () {
|
||||
final input = <Object?, Object?>{
|
||||
"user": <Object?, Object?>{
|
||||
"name": "Hans",
|
||||
"stats": <Object?>[1, 2, true],
|
||||
},
|
||||
42: <Object?>[
|
||||
<Object?, Object?>{"nested": "value"},
|
||||
],
|
||||
};
|
||||
|
||||
final result = input.toJsonValue() as Map<String, Object?>;
|
||||
|
||||
expect(result["42"], isA<List<Object?>>());
|
||||
expect(result["user"], isA<Map<String, Object?>>());
|
||||
final user = result["user"]! as Map<String, Object?>;
|
||||
expect(user["name"], "Hans");
|
||||
expect(user["stats"], <Object?>[1, 2, true]);
|
||||
});
|
||||
|
||||
test("toJsonValue stringifies unsupported leaves", () {
|
||||
final now = DateTime.utc(2026, 1, 1);
|
||||
|
||||
expect(now.toJsonValue(), now.toString());
|
||||
});
|
||||
|
||||
test("fromJsonValue recursively decodes nested maps and lists", () {
|
||||
final input = <String, Object?>{
|
||||
"metadata": <String, Object?>{
|
||||
"list": <Object?>[
|
||||
<String, Object?>{"ok": true},
|
||||
7,
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
final result = input.fromJsonValue() as Map<String, Object?>;
|
||||
final metadata = result["metadata"]! as Map<String, Object?>;
|
||||
final list = metadata["list"]! as List<Object?>;
|
||||
|
||||
expect((list.first as Map<String, Object?>)["ok"], isTrue);
|
||||
expect(list[1], 7);
|
||||
});
|
||||
|
||||
test("fromJsonMap returns null when value is not a map", () {
|
||||
expect(("not a map" as Object?).fromJsonMap(), isNull);
|
||||
expect((null as Object?).fromJsonMap(), isNull);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user