platform/packages/support/lib/src/namespaced_item_resolver.dart

189 lines
5.4 KiB
Dart

/// A class for resolving dot-notated strings into items.
///
/// While Dart doesn't support namespaces directly, this class provides
/// functionality for resolving dot-notated strings into items from a
/// data structure, similar to Laravel's NamespacedItemResolver.
class NamespacedItemResolver {
/// The separator used in the segments.
final String separator;
/// Create a new namespaced item resolver instance.
const NamespacedItemResolver([this.separator = '.']);
/// Parse a key into its segments.
List<String> parseKey(String key) {
if (key.isEmpty) return [];
return key.split(separator);
}
/// Get an item from an array using "dot" notation.
T? get<T>(dynamic target, String key, [T? defaultValue]) {
if (key.isEmpty) return target as T?;
final segments = parseKey(key);
if (segments.isEmpty) return target as T?;
dynamic value = target;
for (final segment in segments) {
if (value == null) {
return defaultValue;
}
if (value is Map) {
value = value[segment];
} else if (value is List && _isValidIndex(segment, value.length)) {
value = value[int.parse(segment)];
} else {
return defaultValue;
}
}
return (value ?? defaultValue) as T?;
}
/// Set an item on an array or object using dot notation.
void set(dynamic target, String key, dynamic value) {
if (key.isEmpty) return;
final segments = parseKey(key);
if (segments.isEmpty) return;
dynamic current = target;
for (var i = 0; i < segments.length - 1; i++) {
final segment = segments[i];
final nextSegment = segments[i + 1];
if (current is! Map) return;
// Initialize the current segment if needed
if (!current.containsKey(segment)) {
if (_isNumeric(nextSegment)) {
current[segment] = [];
} else {
current[segment] = {};
}
} else if (_isNumeric(nextSegment) && current[segment] is! List) {
current[segment] = [];
}
current = current[segment];
// Handle array indices
if (_isNumeric(nextSegment)) {
final index = int.parse(nextSegment);
if (current is List) {
// Ensure list has enough capacity
while (current.length <= index) {
current.add(null);
}
// If there are more segments after this, ensure we have a map at this index
if (i + 2 < segments.length && !_isNumeric(segments[i + 2])) {
if (current[index] == null || current[index] is! Map) {
current[index] = {};
}
// Navigate to the map at this index
current = current[index];
i++; // Skip the numeric segment since we've handled it
}
}
}
}
// Handle the final segment
final lastSegment = segments.last;
if (current is Map) {
current[lastSegment] = value;
} else if (current is List) {
if (_isNumeric(lastSegment)) {
final index = int.parse(lastSegment);
while (current.length <= index) {
current.add(null);
}
current[index] = value;
} else {
// We're trying to set a property on a map within the array
final parentSegment = segments[segments.length - 2];
if (_isNumeric(parentSegment)) {
final index = int.parse(parentSegment);
if (index < current.length) {
if (current[index] == null || current[index] is! Map) {
current[index] = <String, dynamic>{};
}
(current[index] as Map)[lastSegment] = value;
}
}
}
}
}
/// Remove an item from an array using "dot" notation.
void remove(dynamic target, String key) {
if (key.isEmpty) return;
final segments = parseKey(key);
if (segments.isEmpty) return;
dynamic current = target;
for (var i = 0; i < segments.length - 1; i++) {
final segment = segments[i];
if (current is! Map || !current.containsKey(segment)) {
return;
}
current = current[segment];
}
if (current is Map) {
current.remove(segments.last);
} else if (current is List &&
_isValidIndex(segments.last, current.length)) {
current.removeAt(int.parse(segments.last));
}
}
/// Check if an item or items exist in using "dot" notation.
bool has(dynamic target, dynamic key) {
if (key is List) {
return key.every((k) => has(target, k));
}
if (key is! String) return false;
if (key.isEmpty) return false;
final segments = parseKey(key);
if (segments.isEmpty) return false;
dynamic current = target;
for (final segment in segments) {
if (current == null) return false;
if (current is Map) {
if (!current.containsKey(segment)) return false;
current = current[segment];
} else if (current is List) {
if (!_isValidIndex(segment, current.length)) return false;
current = current[int.parse(segment)];
} else {
return false;
}
}
return true;
}
/// Check if a string represents a valid numeric index.
bool _isNumeric(String str) {
if (str.isEmpty) return false;
return int.tryParse(str) != null;
}
/// Check if a string represents a valid index for a list.
bool _isValidIndex(String str, int length) {
if (!_isNumeric(str)) return false;
final index = int.parse(str);
return index >= 0 && index < length;
}
}