1

API がクライアントから JSON データを受信して​​います。

スキーマを使用して、受け取ったデータの検証と強制を実行したいと考えていますが、追加の要件が 1 つあります。スキーマに記述されていないマップ キーがある場合は、検証に失敗するのではなく、無視して削除します (これは、私のクライアントは、私が気にかけているものと一緒にいくつかの「ガベージ」プロパティを送ってくるかもしれません.私はそれに対して寛容になりたい.)

一言で言えば、select-keys検証/強制の前に、スキーマを使用して入力データに対して「深い」を実行したいと思います。

必要なものの例:

(require '[schema.core :as sc])
(def MySchema {:a sc/Int
               :b {:c sc/Str
                   (sc/optional-key :d) sc/Bool}
               :e [{:f sc/Inst}]})

(sanitize-and-validate
  MySchema
  {:a 2
   :b {:c "hello"
       :$$garbage-key 32}
   :e [{:f #inst "2015-07-23T12:29:51.822-00:00" :garbage-key 42}]
   :_garbage-key1 "woot"})
=> {:a 2
    :b {:c "hello"}
    :e [{:f #inst "2015-07-23T12:29:51.822-00:00"}]}

これを行う信頼できる方法をまだ見つけていません。

  1. ウォーカーがキーにアクセスできないように見えるため、カスタム変換では実行できないようです。
  2. マップ スキーマとスカラー スキーマを一般的な方法で区別するのは難しいため、手動でスキーマを調べようとしてもうまくいきませんでした。また、スキーマが持つことができるすべての形状を説明することも困難です。

私が見ていない明らかな方法はありますか?

ありがとう!

4

4 に答える 4

1

スキーマの READMEから:

キーワードの特殊なケースでは、{:foo s/Str :bar s/Keyword} のように必須キーを省略できます。また、特定のオプション キーを指定し、特定のキーを汎用スキーマと組み合わせて、残りのキーと値のマッピングを行うこともできます。

(def FancyMap
  "If foo is present, it must map to a Keyword.  Any number of additional
   String-String mappings are allowed as well."
  {(s/optional-key :foo) s/Keyword
    s/Str s/Str})

(s/validate FancyMap {"a" "b"})

(s/validate FancyMap {:foo :f "c" "d" "e" "f"})

したがって、特定のキー (s/optional-key例のようなもの、またはs/required-key必要に応じて必要なキー) とは別に、次のような追加の「リラックスした」キーを使用できます。

(def MySchema {:a sc/Int
               :b {:c sc/Str
                   (sc/optional-key :d) sc/Bool
                   s/Any s/Any}
               :e [{:f sc/Inst}]})

編集:garbage:メタデータを追加し、ウォーカーでそれらのエントリを破棄することで、これを行う「ハッキーな」方法を見つけました:

(def Myschema {:a s/Int
               :b {:c s/Str
                   (s/optional-key :d) s/Bool
                   (with-meta s/Any {:garbage true}) s/Any}
               :e [{:f s/Inst}]
               (with-meta s/Any {:garbage true}) s/Any})

(defn garbage? [s]
  (and (associative? s)
       (:garbage (meta (:kspec s)))))

(defn discard-garbage [schema]
  (s/start-walker
    (fn [s]
      (let [walk (s/walker s)]
        (fn [x]
          (let [result (walk x)]
            (if (garbage? s)
              (do (println "found garbage" x)
                  nil)
              result)))))
    schema))

((discard-garbage Myschema) data)
;=> :a 2, :b {:c "hello"}, :e [{:f #inst "2015-07-23T12:29:51.822-00:00"}]}
于 2015-07-23T13:25:47.080 に答える
1

これには、「select-schema」と呼ばれるスキーマ ツールがあります。https://github.com/metosin/schema-tools#select-schemaを参照してください

ページから:

スキーマを選択

不正なスキーマ キーを除外する (強制を使用):

(st/select-schema {:street "Keskustori 8"
                   :city "Tampere"
                   :description "Metosin HQ" ; disallowed-key
                   :country {:weather "-18" ; disallowed-key
                             :name "Finland"}}
                  Address)
; {:city "Tampere", :street "Keskustori 8", :country {:name "Finland"}}

1 回のスイープで追加の Json 強制を伴う強制を使用して、不正なスキーマ マップ キーを除外します。

(s/defschema Beer {:beer (s/enum :ipa :apa)})

(def ipa {:beer "ipa" :taste "good"})

(st/select-schema ipa Beer)
; clojure.lang.ExceptionInfo: Could not coerce value to schema: {:beer (not (#{:ipa :apa} "ipa"))}
;     data: {:type :schema.core/error,
;            :schema {:beer {:vs #{:ipa :apa}}},
;            :value {:beer "ipa", :taste "good"},
;            :error {:beer (not (#{:ipa :apa} "ipa"))}}

(require '[schema.coerce :as sc])

(st/select-schema ipa Beer sc/json-coercion-matcher)
; {:beer :ipa}
于 2017-09-27T16:05:30.747 に答える
0

これが別のアプローチです(以下のコード):

  1. 削除するプロパティと照合するカスタムGarbageスキーマ タイプを定義します。不明なプロパティをすべて削除したい場合schema.core/Anyは、スキーマのキーとして使用できます (これについて教えてくれた Colin Yates の功績によるものです)。
  2. 強制ステップとして、削除するすべての値をガベージ型のインスタンスに強制することによって「フラグ」を立てます。
  3. データ構造をトラバースして、すべてのフラグを取り除きます。

これには、Schema の内部構造 (執筆時点ではまだアルファ版) についてほとんど仮定しないという利点と、少なくとも 2 つの欠点があります。

  1. データが Clojure マップとシーケンスの組み合わせであると仮定します (JSON 入力の場合は実際には問題になりません)。
  2. データ構造の別のトラバーサルを追加しますが、パフォーマンスの観点からは最適ではない可能性があります。

(require '[schema.core :as s])
(require '[schema.coerce :as sco])
(require '[schema.utils :as scu])

(deftype ^:private GarbageType [])
(def ^:private garbage-const (GarbageType.))

(def Garbage "Garbage schema, use it to flag schema attributes to be removed by `cleaner`." GarbageType)

(defn garbage-flagging-matcher "schema.coerce matcher to detect and flag garbage values." [schema]
  (cond (= schema Garbage) (constantly garbage-const)
        :else identity))

(defn- garbage-flagger "Accepts a schema (supposedly that uses Garbage as a sub-schema), and returns a function that flags garbage values by coercing them to `garbage-const`"
  [schema] (sco/coercer schema garbage-flagging-matcher))

(defn clean-garbage "Accepts a clojure data structures, and removes the values equal to `garbage-const."
  [v]
  (cond
    (= garbage-const v) nil
    (map? v) (->> v seq
                  (reduce (fn [m [k nv]]
                            (if (= garbage-const nv)
                              (dissoc m k)
                              (assoc m k (clean-garbage nv)))
                            ) v))
    (vector? v) (->> v (remove #(= % garbage-const)) (map clean-garbage) vec)
    (sequential? v) (->> v (remove #(= % garbage-const)) (map clean-garbage) doall)
    :else v
    ))

(defn cleaner "Accepts a Schema, which presumably uses Garbage to match illegal values, and returns a function that accepts a data structure (potentially an instance of the schema) and will remove its values that are not anticipated in the schema, e.g illegal map keys."
  [schema]
  (let [flag (garbage-flagger schema)]
    (fn [data]
      (-> data flag clean-garbage)
      )))

;; Example

(def MySchema {:a s/Int
               :b {:c  s/Str
                   (s/optional-key :d) s/Bool
                   s/Any Garbage}
               :e [{:f s/Inst
                    s/Any Garbage}]
               s/Any Garbage})

((cleaner MySchema) {:a 1
                       :garbage-key "hello"
                       :b {:c "Hellow world"
                           :d false
                           42432424 23/2}
                       :e [{:f #inst "2015-07-23T15:49:33.073-00:00"
                            'a-garbage-key "remove me!!"
                            "another garbage key" :remove-me!!}
                           {:f #inst "2015-07-23T15:53:33.073-00:00"}]})
  => {:a 1
      :b {:c "Hellow world"
          :d false}
      :e [{:f #inst "2015-07-23T15:49:33.073-00:00"}
          {:f #inst "2015-07-23T15:53:33.073-00:00"}]}
于 2015-07-23T17:41:03.890 に答える