HS: Values dict insertion order (+2 tests)

Dict-set! keys iterate in scrambled order, so Values|FormEncoded and
Values|JSONString produced output in the wrong order. Fix: hs-values-absorb
now tracks insertion order in a hidden `_order` list on the dict itself.
hs-coerce FormEncoded/JSONString paths read `_order` when present and
iterate in that order (filtering the marker key out).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-23 21:35:47 +00:00
parent 35c72e2a13
commit e59c0b8e0a
2 changed files with 148 additions and 70 deletions

View File

@@ -794,16 +794,26 @@
((string? v) (hs-json-escape v)) ((string? v) (hs-json-escape v))
((list? v) (str "[" (join "," (map hs-json-stringify v)) "]")) ((list? v) (str "[" (join "," (map hs-json-stringify v)) "]"))
((dict? v) ((dict? v)
(str (let
"{" ((ks (or (get v "_order") (filter (fn (k) (not (= k "_order"))) (keys v)))))
(join (str
"," "{"
(map (join
(fn ","
(k) (filter
(str (hs-json-escape k) ":" (hs-json-stringify (get v k)))) (fn (s) (not (= s "")))
(keys v))) (map
"}")) (fn
(k)
(if
(= k "_order")
""
(str
(hs-json-escape k)
":"
(hs-json-stringify (get v k)))))
ks)))
"}")))
(true (hs-json-escape (str v)))))) (true (hs-json-escape (str v))))))
;; Collection: split by ;; Collection: split by
(define (define
@@ -843,18 +853,25 @@
((= type-name "FormEncoded") ((= type-name "FormEncoded")
(if (if
(dict? value) (dict? value)
(join (let
"&" ((ks (or (get value "_order") (filter (fn (k) (not (= k "_order"))) (keys value)))))
(map (join
(fn "&"
(k) (filter
(let (fn (s) (not (= s "")))
((v (get value k))) (map
(if (fn
(list? v) (k)
(join "&" (map (fn (item) (str k "=" item)) v)) (if
(str k "=" v)))) (= k "_order")
(keys value))) ""
(let
((v (get value k)))
(if
(list? v)
(join "&" (map (fn (item) (str k "=" item)) v))
(str k "=" v)))))
ks))))
(str value))) (str value)))
((or (= type-name "Fixed") (= type-name "Fixed:") (starts-with? type-name "Fixed:")) ((or (= type-name "Fixed") (= type-name "Fixed:") (starts-with? type-name "Fixed:"))
(let (let
@@ -932,18 +949,32 @@
(let (let
((kids (host-get node "children"))) ((kids (host-get node "children")))
(when (when
(and (not (nil? kids)) (list? kids)) (not (nil? kids))
(let (cond
((n (len kids))) ((list? kids)
(define (let
each ((n (len kids)))
(fn (define
(i) each
(when (fn
(< i n) (i)
(walk (nth kids i)) (when
(each (+ i 1))))) (< i n)
(each 0))))))))) (walk (nth kids i))
(each (+ i 1)))))
(each 0)))
(true
(let
((n (or (host-get kids "length") 0)))
(define
each-h
(fn
(i)
(when
(< i n)
(walk (host-get kids i))
(each-h (+ i 1)))))
(each-h 0)))))))))))
(walk root) (walk root)
acc))) acc)))
@@ -1015,7 +1046,15 @@
(dict-set! acc name (append existing (list v))) (dict-set! acc name (append existing (list v)))
(dict-set! acc name (list existing v))) (dict-set! acc name (list existing v)))
acc))) acc)))
(true (do (dict-set! acc name v) acc)))))))) (true
(begin
(dict-set! acc name v)
(let
((ord (or (get acc "_order") (list))))
(when
(not (some (fn (x) (= x name)) ord))
(dict-set! acc "_order" (append ord (list name)))))
acc))))))))
(define (define
hs-as-values hs-as-values

View File

@@ -794,16 +794,26 @@
((string? v) (hs-json-escape v)) ((string? v) (hs-json-escape v))
((list? v) (str "[" (join "," (map hs-json-stringify v)) "]")) ((list? v) (str "[" (join "," (map hs-json-stringify v)) "]"))
((dict? v) ((dict? v)
(str (let
"{" ((ks (or (get v "_order") (filter (fn (k) (not (= k "_order"))) (keys v)))))
(join (str
"," "{"
(map (join
(fn ","
(k) (filter
(str (hs-json-escape k) ":" (hs-json-stringify (get v k)))) (fn (s) (not (= s "")))
(keys v))) (map
"}")) (fn
(k)
(if
(= k "_order")
""
(str
(hs-json-escape k)
":"
(hs-json-stringify (get v k)))))
ks)))
"}")))
(true (hs-json-escape (str v)))))) (true (hs-json-escape (str v))))))
;; Collection: split by ;; Collection: split by
(define (define
@@ -843,18 +853,25 @@
((= type-name "FormEncoded") ((= type-name "FormEncoded")
(if (if
(dict? value) (dict? value)
(join (let
"&" ((ks (or (get value "_order") (filter (fn (k) (not (= k "_order"))) (keys value)))))
(map (join
(fn "&"
(k) (filter
(let (fn (s) (not (= s "")))
((v (get value k))) (map
(if (fn
(list? v) (k)
(join "&" (map (fn (item) (str k "=" item)) v)) (if
(str k "=" v)))) (= k "_order")
(keys value))) ""
(let
((v (get value k)))
(if
(list? v)
(join "&" (map (fn (item) (str k "=" item)) v))
(str k "=" v)))))
ks))))
(str value))) (str value)))
((or (= type-name "Fixed") (= type-name "Fixed:") (starts-with? type-name "Fixed:")) ((or (= type-name "Fixed") (= type-name "Fixed:") (starts-with? type-name "Fixed:"))
(let (let
@@ -932,18 +949,32 @@
(let (let
((kids (host-get node "children"))) ((kids (host-get node "children")))
(when (when
(and (not (nil? kids)) (list? kids)) (not (nil? kids))
(let (cond
((n (len kids))) ((list? kids)
(define (let
each ((n (len kids)))
(fn (define
(i) each
(when (fn
(< i n) (i)
(walk (nth kids i)) (when
(each (+ i 1))))) (< i n)
(each 0))))))))) (walk (nth kids i))
(each (+ i 1)))))
(each 0)))
(true
(let
((n (or (host-get kids "length") 0)))
(define
each-h
(fn
(i)
(when
(< i n)
(walk (host-get kids i))
(each-h (+ i 1)))))
(each-h 0)))))))))))
(walk root) (walk root)
acc))) acc)))
@@ -1015,7 +1046,15 @@
(dict-set! acc name (append existing (list v))) (dict-set! acc name (append existing (list v)))
(dict-set! acc name (list existing v))) (dict-set! acc name (list existing v)))
acc))) acc)))
(true (do (dict-set! acc name v) acc)))))))) (true
(begin
(dict-set! acc name v)
(let
((ord (or (get acc "_order") (list))))
(when
(not (some (fn (x) (= x name)) ord))
(dict-set! acc "_order" (append ord (list name)))))
acc))))))))
(define (define
hs-as-values hs-as-values