如何在 Clojure 中转换对象键和值
How to transform object keys and values in Clojure
我正在使用 Datomic,虽然这对这个问题来说并不是特别重要。但它通常 returns 命名空间键(并且枚举值也被 returned 作为命名空间关键字)。我想翻译潜在的嵌套结构以从键和值中剥离名称空间(以及字符串化枚举的值)。我这样做是因为我将 return JSON REST API 中的结果,并且命名空间在该上下文中没有多大意义。这是一个简单的示例结构:
{
:person/name "Kevin"
:person/age 99
:person/gender :gender/M
:person/address {
:address/state :state/NY
:address/city "New York"
:address/zip "99999"
}
}
我希望翻译成:
{
:name "Kevin"
:age 99
:gender "M"
:address {
:state "NY"
:city "New York"
:zip "99999"
}
}
我知道我能做的一件事是使用 (postwalk-replace {:person/name :name :person/age :age :person/gender :gender :person/address :address :address/city :city :address/state :state :address/zip :zip} the-entity)
,它涵盖了键,但不包括值。
我还有哪些其他选择?
您可以使用 clojure.walk/postwalk
。简单版本不区分关键字作为映射中的键或值,只是将所有键转换为字符串:
(def data {:person/name "Kevin"
:person/age 99
:person/gender :gender/M
:person/address {:address/state :state/NY
:address/city "New York"
:address/zip "99999"}})
(clojure.walk/postwalk
(fn [x]
(if (keyword? x)
(name x)
x))
data)
;; => => {"name" "Kevin", "age" 99, "gender" "M", "address" {"state" "NY", "city" "New York", "zip" "99999"}}
要准确实现您想要的功能,您需要分别处理映射中的键和值:
(defn transform-keywords [m]
(into {}
(map (fn [[k v]]
(let [k (if (keyword? k) (keyword (name k)) k)
v (if (keyword? v) (name v) v)]
[k v]))
m)))
(clojure.walk/postwalk
(fn [x]
(if (map? x)
(transform-keywords x)
x))
data)
;; => => {:name "Kevin", :age 99, :gender "M", :address {:state "NY", :city "New York", :zip "99999"}}
附带说明:根据我的经验,系统边界处的命名空间限定键和非命名空间限定键之间的阻抗不匹配可能是一个持续的痛苦;更重要的是,拥有命名空间限定的键在代码清晰度(非常好的数据可追溯性)方面具有显着优势。
所以我不会轻易放弃命名空间限定的键。如果 EDN 的命名空间语法(带点和斜杠)不适合您的 API 的使用者,您甚至可能想要使用更传统的东西,例如下划线(例如 :person_name
而不是 :person/name
);有点丑陋,但它仍然为您提供命名空间限定键的大部分好处,您甚至不需要转换数据结构,Datomic 也不会介意。
我正在使用 Datomic,虽然这对这个问题来说并不是特别重要。但它通常 returns 命名空间键(并且枚举值也被 returned 作为命名空间关键字)。我想翻译潜在的嵌套结构以从键和值中剥离名称空间(以及字符串化枚举的值)。我这样做是因为我将 return JSON REST API 中的结果,并且命名空间在该上下文中没有多大意义。这是一个简单的示例结构:
{
:person/name "Kevin"
:person/age 99
:person/gender :gender/M
:person/address {
:address/state :state/NY
:address/city "New York"
:address/zip "99999"
}
}
我希望翻译成:
{
:name "Kevin"
:age 99
:gender "M"
:address {
:state "NY"
:city "New York"
:zip "99999"
}
}
我知道我能做的一件事是使用 (postwalk-replace {:person/name :name :person/age :age :person/gender :gender :person/address :address :address/city :city :address/state :state :address/zip :zip} the-entity)
,它涵盖了键,但不包括值。
我还有哪些其他选择?
您可以使用 clojure.walk/postwalk
。简单版本不区分关键字作为映射中的键或值,只是将所有键转换为字符串:
(def data {:person/name "Kevin"
:person/age 99
:person/gender :gender/M
:person/address {:address/state :state/NY
:address/city "New York"
:address/zip "99999"}})
(clojure.walk/postwalk
(fn [x]
(if (keyword? x)
(name x)
x))
data)
;; => => {"name" "Kevin", "age" 99, "gender" "M", "address" {"state" "NY", "city" "New York", "zip" "99999"}}
要准确实现您想要的功能,您需要分别处理映射中的键和值:
(defn transform-keywords [m]
(into {}
(map (fn [[k v]]
(let [k (if (keyword? k) (keyword (name k)) k)
v (if (keyword? v) (name v) v)]
[k v]))
m)))
(clojure.walk/postwalk
(fn [x]
(if (map? x)
(transform-keywords x)
x))
data)
;; => => {:name "Kevin", :age 99, :gender "M", :address {:state "NY", :city "New York", :zip "99999"}}
附带说明:根据我的经验,系统边界处的命名空间限定键和非命名空间限定键之间的阻抗不匹配可能是一个持续的痛苦;更重要的是,拥有命名空间限定的键在代码清晰度(非常好的数据可追溯性)方面具有显着优势。
所以我不会轻易放弃命名空间限定的键。如果 EDN 的命名空间语法(带点和斜杠)不适合您的 API 的使用者,您甚至可能想要使用更传统的东西,例如下划线(例如 :person_name
而不是 :person/name
);有点丑陋,但它仍然为您提供命名空间限定键的大部分好处,您甚至不需要转换数据结构,Datomic 也不会介意。