首页 > 解决方案 > 有没有一种方法可以重用 EDN 中已定义键的值?

问题描述

有没有办法可以重用 EDN 中定义的键值?

例如:如果我有以下 EDN

{
   :abc "value1"
   :def :abc
}

当我在 Clojure 中阅读上面的 EDN 时,我想要传递 :abc 的值 'def' 键 value1。但目前 :abc 字符串在我使用 EDN 读取字符串函数读取时传递

标签: clojureedn

解决方案


普通的 EDN 中没有这样的东西。但是您可以构建一个两阶段传递,或者通过首先替换 EDN 字符串本身的内容,或者在通过操作数据结构解析 EDN 之后更好地执行此操作,例如:

(require '[clojure.edn :as edn])
(require '[clojure.walk :as walk])

(defrecord self [key])
(defn tag-self
  [v]
  (->self v))

(defn custom-readers []
  {'x/self tag-self})

(defn replace-self
  [coll]
  (walk/prewalk
   (fn [form]
     (if (map? form)
       (reduce-kv
        (fn [acc k v]
          (assoc acc k (if (= self (type v))
                         (get form (:key v))
                         v)))
        {}
        form)
       form))
   coll))

(replace-self
 (edn/read-string
  {:readers (custom-readers)}
  "[{:abc \"value1\"
     :def #x/self :abc}
    {:abc {:bbb \"value2\"
           :def #x/self :bbb}
     :def #x/self :abc
     :xyz {:aaa \"value3\"
           :nope #x/self :abc
           :def #x/self :aaa}}]"))

将返回:

[{:abc "value1", :def "value1"}
 {:abc {:bbb "value2", :def "value2"},
  :def {:bbb "value2", :def "value2"},
  :xyz {:aaa "value3", :nope nil, :def "value3"}}]

The trick is that I mark the references to another key of the current map using the #x/self tag reader that I created, which will wrap the value in a self record. Now we just walk over the returned data-structure, and every time we encounter a self type, we replace it by the value of its :key attribute in the same map.

The above replace-self walker is recursive, so you can see that it can handle nested maps and it can even replace self references inside map that are themselves self referenced. It does local scoping only though, so :nope #x/self :abc did not get replaced by the value of the :abc in the parent map, and since there was no :abc key in its own map, its value was set to nil.

The custom tag is optional, I did it this way cause now it works for any type of key. But you could use keyword? instead in replace-self as well if you want this to be the behavior for them:

(defn replace-self
  [coll]
  (walk/prewalk
   (fn [form]
     (if (map? form)
       (reduce-kv
        (fn [acc k v]
          (assoc acc k (if (keyword? v)
                         (v form)
                         v)))
        {}
        form)
       form))
   coll))

(replace-self
 (edn/read-string
  "[{:abc \"value1\"
     :def :abc}
    {:abc {:bbb \"value2\"
           :def :bbb}
     :def :abc
     :xyz {:aaa \"value3\"
           :nope :abc
           :def :aaa}}]"))

This will return the same as before, just no need for a custom tag and the self record:

[{:abc "value1", :def "value1"}
 {:abc {:bbb "value2", :def "value2"},
  :def {:bbb "value2", :def "value2"},
  :xyz {:aaa "value3", :nope nil, :def "value3"}}]

推荐阅读