Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
## 1.7.1-wip

* [FIX] restore robust cyclic detection when `filter` callbacks wrap values in fresh containers by tracking object identity before filter/date transformations
* [FIX] improve deep path handling in encoder key materialization/dot-encoding via iterative `KeyPathNode` caching (avoids recursive overflow risk and reuses ancestor caches)
* [CHORE] refactor encoder internals to share immutable frame config through new `EncodeConfig` and reduce per-frame option duplication
* [CHORE] replace `weak_map` usage in encode cycle tracking with identity-based `Set<Object>` side-channel and remove `weak_map` dependency
* [CHORE] expand encoder regression coverage with new tests for filter-wrapped cycles, `KeyPathNode` caching/encoding edge cases, and `EncodeConfig.copyWith` sentinel behavior
* [CHORE] refine decode internals with clearer duplicate-handling branching and a small dot-decoding fast-path guard (`cleanRoot.contains('%2')`)

## 1.7.0

* [FEAT] add `DecodeOptions.throwOnLimitExceeded` for strict limit enforcement on parameter, list, and depth overflows
Expand Down
27 changes: 18 additions & 9 deletions lib/src/extensions/decode.dart
Original file line number Diff line number Diff line change
Expand Up @@ -191,10 +191,19 @@ extension _$Decode on QS {

// Duplicate key policy: combine/first/last (default: combine).
final bool existing = obj.containsKey(key);
if (existing && options.duplicates == Duplicates.combine) {
obj[key] = Utils.combine(obj[key], val, listLimit: options.listLimit);
} else if (!existing || options.duplicates == Duplicates.last) {
obj[key] = val;
switch ((existing, options.duplicates)) {
case (true, Duplicates.combine):
// Existing key + `combine` policy: merge old/new values.
obj[key] = Utils.combine(obj[key], val, listLimit: options.listLimit);
break;
case (false, _):
case (true, Duplicates.last):
// New key, or `last` policy: store the current value.
obj[key] = val;
break;
case (true, Duplicates.first):
// Existing key + `first` policy: keep the original value.
break;
}
}

Expand Down Expand Up @@ -285,7 +294,7 @@ extension _$Decode on QS {
final bool wasBracketed = root.startsWith('[') && root.endsWith(']');
final String cleanRoot =
wasBracketed ? root.slice(1, root.length - 1) : root;
String decodedRoot = options.decodeDotInKeys
String decodedRoot = options.decodeDotInKeys && cleanRoot.contains('%2')
? cleanRoot.replaceAll('%2E', '.').replaceAll('%2e', '.')
: cleanRoot;

Expand All @@ -301,8 +310,8 @@ extension _$Decode on QS {
int opens = 0, closes = 0;
for (int k = 0; k < decodedRoot.length; k++) {
final cu = decodedRoot.codeUnitAt(k);
if (cu == 0x5B) opens++;
if (cu == 0x5D) closes++;
if (cu == 0x5B) opens++; // '['
if (cu == 0x5D) closes++; // ']'
}
if (opens > closes) {
decodedRoot = decodedRoot.substring(0, decodedRoot.length - 1);
Expand Down Expand Up @@ -404,9 +413,9 @@ extension _$Decode on QS {
// Balance nested '[' and ']' within this group.
while (i < n) {
final int cu = key.codeUnitAt(i);
if (cu == 0x5B) {
if (cu == 0x5B /* '[' */) {
level++;
} else if (cu == 0x5D) {
} else if (cu == 0x5D /* ']' */) {
level--;
if (level == 0) {
close = i;
Expand Down
Loading