【问题标题】:Clojure: Ensure data integrity when creating a Record from a Map?Clojure:从地图创建记录时确保数据完整性?
【发布时间】:2018-03-18 11:28:46
【问题描述】:

我正在学习 Clojure 并喜欢它,但发现 Records 中的不一致让我感到困惑:为什么在创建新 Record 时默认映射构造函数 (map->Whatever) 不检查数据完整性?例如:

user=> (defrecord Person [first-name last-name])
#<Class@46ffda99 user.Person>
user=> (map->Person {:first-name "Rich" :last-name "Hickey"})
#user.Person {:first-name "Rich" :last-name "Hickey"}
user=> (map->Person {:first-game "Rich" :last-name "Hickey"})
#user.Person {:first-game "Rich" :first-name nil :last-name "Hickey"}

我相信 Map 不需要定义 Record 定义中的所有字段,它也允许包含不属于 Record 定义的额外字段。另外我知道我可以定义自己的构造函数来包装默认构造函数,并且我认为:post 条件可以用于检查正确(和全面)的记录创建(未能成功使其工作)。

我的问题是:是否有惯用的 Clojure 方法在从 Map 构造记录期间验证数据?而且,我在这里缺少关于 Records 的一些东西吗?

谢谢。

【问题讨论】:

  • 老实说,我很少需要将地图转换为记录。我只是使用原始构造函数。用例是什么?如果你正在序列化,你可以使用 EDN 来保证它的安全。
  • 我只是重构了一个使用地图的旧代码问题,现在使用记录作为理解其属性的一种方式。 JSON -> 地图向量将变为 JSON -> 记录向量。感谢 EDN 参考。将进行调查。

标签: clojure


【解决方案1】:

我认为您的全面性要求已经很具体了,所以我所知道的没有任何内置功能可以涵盖这一点。

您现在可以做的一件事是使用 clojure.spec 为您的构造函数提供 s/fdef(然后对其进行检测)。

(require '[clojure.spec.alpha :as s]
         '[clojure.spec.test.alpha :as stest])

(defrecord Person [first-name last-name])

(s/fdef map->Person
  :args (s/cat :map (s/keys :req-un [::first-name ::last-name])))

(stest/instrument `map->Person)

(map->Person {:first-name "Rich", :last-name "Hickey"})
(map->Person {:first-game "Rich", :last-name "Hickey"})  ; now fails

(如果为 ::first-name::last-name 定义了规范,则也会检查这些规范。)

【讨论】:

  • 谢谢-我认为规范可能与此答案有关,我将了解更多信息。我对如何防止这种情况发生感兴趣:(map-&gt;Person {:first-name "Rich", :last-name "Hickey", :pet "Spot"}) ; =&gt; #user.Person{:first-name "Rich", :last-name "Hickey", :pet "Spot"}
  • @ericky 以下规范确保只有键 :a:b 在地图中,根据需要进行调整:(s/and (s/keys :req-un [::a ::b]) #(= 2 (count %)))
  • @ericky 没有简单的方法可以将键限制为一组封闭的允许键并禁止所有其他键,这是故意的。据我了解,Clojure 设计者提倡“开放”规范(也许更普遍的系统“开放”以进行扩展)。根据该观点,应该始终可以使用其他密钥来丰富您的数据,并且您的系统应该容忍这种情况(允许丰富的数据流过它)......
【解决方案2】:

另一个选项是to use Plumatic Schema,用于创建一个包装“构造函数”函数,指定允许的键。例如:

(def FooBar {(s/required-key :foo) s/Str (s/required-key :bar) s/Keyword})

(s/validate FooBar {:foo "f" :bar :b})
;; {:foo "f" :bar :b}

(s/validate FooBar {:foo :f})
;; RuntimeException: Value does not match schema:
;;  {:foo (not (instance? java.lang.String :f)),
;;   :bar missing-required-key}

第一行定义了一个只接受如下映射的模式:

{ :foo "hello"  :bar :some-kw }

你的包装构造函数看起来像:

(def NameMap {(s/required-key :first-name) s/Str (s/required-key :last-name) s/Str})

(s/defn safe->person 
  [name-map :- NameMap]
  (map->Person name-map))

(s/defn safe->person-2
  [name-map]
  (assert (= #{:first-name :last-name} (set (keys name-map))))
  (map->Person name-map))

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2014-09-30
    • 1970-01-01
    • 1970-01-01
    • 2015-03-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多