@@ -218,16 +218,44 @@ enum JSONValue: Codable, Hashable, Sendable, Comparable {
218218 // the same fallback `LogStream.<` already applies.
219219 let order = a.localizedStandardCompare(b)
220220 return order == .orderedAscending || (order == .orderedSame && a < b)
221− case (.array, .array), (.object, .object):
222− // Same rank, structured: equal values must not be ordered; otherwise
223− // use a deterministic serialization tiebreaker.
224− if lhs == rhs { return false }
225− return lhs.compactJSON < rhs.compactJSON
221+ case (.array(let a), .array(let b)):
222+ return arrayLessThan(a, b)
223+ case (.object(let a), .object(let b)):
224+ return objectLessThan(a, b)
226225 default:
227226 return false
228227 }
229228 }
230229
230+ /// Lexicographic order over array elements using the element-level `<`/`==`.
231+ /// A serialized-string tiebreaker (`compactJSON < compactJSON`) was wrong:
232+ /// `.int(1) == .double(1.0)` are equal yet serialize as `1` vs `1.0`, so two
233+ /// non-equal values could share a serialization (or two equal ones differ),
234+ /// breaking the strict-weak-ordering `Comparable` requires. Comparing
235+ /// elements directly stays consistent with `==` (arrays are equal iff same
236+ /// length and equal elements).
237+ private static func arrayLessThan(_ a: [JSONValue], _ b: [JSONValue]) -> Bool {
238+ for (x, y) in zip(a, b) where x != y {
239+ return x < y
240+ }
241+ return a.count < b.count
242+ }
243+
244+ /// Deterministic order over objects: compare key/value pairs in sorted-key
245+ /// order. Independent of dictionary iteration order and consistent with `==`
246+ /// (objects are equal iff the same keys map to equal values).
247+ private static func objectLessThan(_ a: [String: JSONValue], _ b: [String: JSONValue]) -> Bool {
248+ let aKeys = a.keys.sorted()
249+ let bKeys = b.keys.sorted()
250+ for (ak, bk) in zip(aKeys, bKeys) {
251+ if ak != bk { return ak < bk }
252+ // Force-unwrap is safe: ak/bk come from each dictionary's own keys.
253+ let av = a[ak]!, bv = b[bk]!
254+ if av != bv { return av < bv }
255+ }
256+ return aKeys.count < bKeys.count
257+ }
258+
231259 /// Total ordering for doubles that keeps `sort()` from trapping on NaN
232260 /// (IEEE comparisons with NaN are always false, which violates the strict
233261 /// weak ordering `Comparable` requires). NaN is ordered after all numbers.