metosin / spec-tools

Clojure(Script) tools for clojure.spec
Eclipse Public License 2.0
592 stars 94 forks source link

select-spec not removing extra keys #78

Closed jeaye closed 7 years ago

jeaye commented 7 years ago

Hey there. select-spec looked like the perfect tool for the job when I was hoping to take a map of X + N keys and pull out only X. Alas, it doesn't seem to be doing anything for me. Perhaps it's a bug, but more likely I'm missing something simple.

user=> (require '[spec-tools.core :as st])
nil

; First try unqualified
user=> (s/def ::meow (s/keys :req-un [::a ::b ::c]))
:user/meow
user=> (st/conform ::meow {:a 1 :b 2 :c 3 :d 4} st/strip-extra-keys-conforming)
{:a 1 :b 2 :c 3 :d 4} ; Nope. :(

; How about qualified?
user=> (s/def ::kitty (s/keys :req [::a ::b ::c]))
:user/kitty
user=> (st/conform ::kitty {::a 1 ::b 2 ::c 3 ::d 4} st/strip-extra-keys-conforming)
{:user/a 1
 :user/b 2
 :user/c 3
 :user/d 4} ; Nope. :(
ikitommi commented 7 years ago

Hi. You need to wrap s/keys into Spec Records (via st/spec) as clojure.spec doesn't support selective runtime conforming out-of-the-box. See https://dev.clojure.org/jira/browse/CLJ-2116 & the README example https://github.com/metosin/spec-tools#map-conforming.

Good thing is that creating the spec record makes this really fast as it captures all the possible keys at creation time and the actual select-spec just runs on select-keys per spec.

I'll add a FAQ into the README for this.

jeaye commented 7 years ago

Ah, indeed, it was something simple I missed. Thanks for the info and for the FAQ entry!

WhittlesJr commented 4 years ago

Looks like it won't work with any merging though, is that right?

ikitommi commented 4 years ago

Do you have an example of merging where it doesn't work?

WhittlesJr commented 4 years ago

Oh, you know what, it's because I have a multi-spec merged in.


(require '[clojure.spec.alpha :as s])
(require '[spec-tools.core :as st])

(s/def ::a int?)
(s/def ::b string?)
(s/def ::c keyword?)

(s/def ::map1 (st/spec (s/keys :req-un [::a])))
(s/def ::map2 (st/spec (s/keys :req-un [::b])))
(s/def ::map3 (st/spec (s/keys :req-un [::c])))

(defmulti myspec :type)
(defmethod myspec 3 [_] ::map2 )

(s/def ::map3 (s/merge ::map1 ::map2 (st/spec (s/multi-spec myspec :type))))

(def test-map-3 {:a 1 :b "hi" :type 3 :c :keep-me :d :remove-me})

(s/valid? ::map3 test-map-3)
;;=> true

(st/select-spec ::map3 test-map-3)
;;=>{:a 1, :b "hi", :type 3, :c :keep-me, :d :remove-me}