如何在Clojure序列化字符串前对JSON/Map结构排序?
Clojure中clojure/data.json序列化时的键排序解决方案
问题场景
你使用clojure.data.json/write-str序列化嵌套Clojure map时,遇到键顺序不符合预期的问题:
- 原始数据:
{ :c 1 :a { :d 0 :c 1 :e { :a 0 :1 4 }}} - 默认输出(键无固定顺序):
{"c":1,"a":{"d":0,"c":1,"e":{"a":0,"1":4}}} - 期望输出(按指定顺序排列键):
{"a":{"c":1,"d":0,"e":{"1":4,"a":0}},"c":1}
核心原因
Clojure默认的hash-map是无序结构,write-str序列化时会按map内部的哈希顺序输出键,无法保证固定顺序。而JavaScript的JSON.stringify可以通过第二个参数指定键顺序,Clojure需要借助有序数据结构来实现类似效果。
解决方案
1. 基于自然排序的有序序列化
如果希望键按字符串自然顺序排列(数字键优先,字母键按字典序),可以把所有嵌套map转换为sorted-map(有序map),再进行序列化:
(require '[clojure.data.json :as json] '[clojure.walk :as walk]) ;; 递归将所有嵌套map转为sorted-map (defn to-sorted-map [m] (if (map? m) (into (sorted-map) (walk/postwalk to-sorted-map m)) m)) (def raw-data { :c 1 :a { :d 0 :c 1 :e { :a 0 :1 4 }}}) (json/write-str (to-sorted-map raw-data))
执行后输出完全符合你的期望:
{"a":{"c":1,"d":0,"e":{"1":4,"a":0}},"c":1}
2. 手动指定自定义键顺序
如果需要严格按照你定义的顺序输出键,可以使用array-map(小map保持插入顺序)或引入有序map库来构造数据:
(require '[clojure.data.json :as json]) ;; 按期望顺序构造有序map (def ordered-data (array-map :a (array-map :c 1 :d 0 :e (array-map :1 4 :a 0)) :c 1)) (json/write-str ordered-data)
这种方式会完全遵循你插入键的顺序生成JSON字符串。如果需要处理大量键或稳定的插入顺序,推荐使用org.clojure/data.ordered库的ordered-map,或者Guava的LinkedHashMap。
注意事项
array-map在键数量超过8个时会自动转为无序的hash-map,如果需要稳定的插入顺序,优先使用专门的有序map实现;sorted-map会按键的自然排序规则排列,适合不需要自定义顺序、只需要统一固定顺序的场景;- 不需要修改原始数据结构的话,也可以自定义
write-str的:formatter参数实现排序,但这种方式代码复杂度更高,不如直接使用有序map直观。
内容的提问来源于stack exchange,提问作者Erik Lilja
相关产品推荐
相关产品推荐

