简体   繁体   中英

Decode, in clojure, a JSON, clojure.data.json & cheshire.core, can't custom decode w/cheshire

My project parses JSONs, with a read/write library, called:

cheshire.core

I was having problems, trying to get the decode (func) to work, so I started messing around with:

data.json

My JSON contains data that consists of a field named "zone" this contains a vector with :keys inside, like so {:zone : [:hand :table]} that is stored into strings within the vector stored like so: {"zone" : ["hand" "table"]}

So I figured out how to convert the sample data using:

(mapv keyword {"zone" : ["hand"]})

which was great, I then needed to figure out how to implement a decoder for cheshire, I couldn't do this with my logic, I only spent like an hour working on this, but I had been using data.json, and the decoder function is relatively easy I think.

I got my project to work, here is some sample code:

(ns clojure-noob.core (:require
                    [cheshire.core :refer [decode]]
                    [clojure.data.json :as j-data]
                    ) (:gen-class))

(defn -main
  "I don't do a whole lot ... yet."
  [& args]
  )

this is using cheshire:

(let [init (decode "{\"zone\" : [\"hand\"]}" true
               (fn [field-name]
                 (if (= field-name "zone")
                   (mapv keyword [])
                   [])))]
  (println (str init)))

this is using data.json:

(defn my-value-reader [key value]
  (if (= key :zone)
    (mapv keyword value)
      value))

(let [init (j-data/read-str
         "{\"zone\" : [\"hand\"]}"
         :value-fn my-value-reader
         :key-fn keyword)]
  (println (str init)))

I want the bottom result of these two from the console:

{:zone ["hand"]}
{:zone [:hand]}

The problem is I would like to do this using cheshire 😎 ps I am reading the factory section of cheshire? maybe this easier?

I would agree with @TaylorWood. Don't mess with the decoder, just do a bite in a time. First, parse json. Second, transform the result.

(def data "{\"zone\" : [\"hand\"]}")

(-> data 
    (cheshire.core/decode true)
    (update-in ["zone"] (partial mapv keyword)))
#=> {:zone [:hand]}

I recommend you use a tool like schema.tools to coerce the input. You can add a second pass that attempts to coerce JSON strings into richer clojure types.

Here's some sample code!

;; require all the dependencies. See links below for libraries you need to add
(require '[cheshire.core :as json])
(require '[schema.core :as s])
(require '[schema.coerce :as sc])
(require '[schema-tools.core :as st])

;; your data (as before)
(def data "{\"zone\" : [\"hand\"]}")

;; a schema that wants an array of keywords
(s/defschema MyData {:zone [s/Keyword]})

;; use `select-schema` along with a JSON coercion matcher
(-> data
  (json/decode true)
  (st/select-schema MyData sc/json-coercion-matcher))

;; output: {:zone [:hand]}

Using defschema to define the shape of data you want gives you a general solution for serializing into JSON while getting the full benefit of Clojure's value types. Instead of explicitly "doing" the work of transforming, your schema describes the expected outcome, and hopefully coercions can do the right thing!

Links to libraries: - https://github.com/plumatic/schema - https://github.com/metosin/schema-tools#coercion

Note: you can do a similar thing with clojure.spec using metosin/spec-tools . Check out their readme for some help.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM