Search code examples
jsonclojurering

Why do I have to flatten nested JSON when using the ring json middleware


I have been writing a clojure application with ring and compojure. I am using the ring.middleware.json middleware for handling JSON so I don't have to serialise and deserialise it myself in my code.

This middleware only seems to correctly parse nested JSON when given in a flattened format, for example if I want to POST nested data to an API route I have to encode it as:

{"task"            : 1,
 "success"         : false,
 "files[0][type]"   : "log",
 "files[0][sha256]" : "adef5c",
 "files[0][url]"    : "s3://url"}

Instead of, what seems to me, more standard JSON:

{"task"    : 1,
 "success" : false,
 "files"   : [{"type" : "log", "sha256" : "adef5c", "url": "s3://url"}]}

Is this indented or a standard way of posting nested JSON? Here is my middleware stack:

(defn middleware [handler]
  (-> handler
    (wrap-json-response)
    (wrap-with-logger)
    (api)))

Solution

  • Here is a full Clojure example of post and get routes that handle JSON or return JSON:

    (ns ring-app.core
      (:require [ring.adapter.jetty :as jetty]
                [compojure.core :as compojure]
                [ring.middleware.keyword-params :refer [wrap-keyword-params]]
                [ring.middleware.json :refer [wrap-json-response wrap-json-body]]
                [ring.util.http-response :as response]
                [clojure.pprint :refer [pprint]]
                [ring.middleware.reload :refer [wrap-reload]]))
    
    (defn json-get [request]
      (response/ok 
        {"task"  1 
         "success"  false  
         "files" [{"type"  "log" "sha256"  "adef5c" "url"  "s3://url"}]}))
    
    (defn json-post [request]
      (let [bpdy (:body request)]
        (prn bpdy)
        (response/ok bpdy)))
    
    (compojure/defroutes handler_
     (compojure/GET "/get" request json-get)
     (compojure/POST "/post" request json-post))
    
    (defn wrap-nocache [handler]
      (fn [request]
        (-> request
            handler
            (assoc-in [:headers "Pragma"] "no-cache"))))
    
    (def handler 
      (-> #'handler_ wrap-nocache wrap-reload wrap-json-response wrap-json-body ) )
    

    The get endpoint returns the nested structure:

    curl http://localhost:3000/get
    ; {"task":1,"success":false,"files":[{"type":"log","sha256":"adef5c","url":"s3://url"}]}                                    
    

    And the POST endpoint parses the json and create a Clojure data structure:

      curl -X post  -d '{"task":1,"success":false,"files":[{"type":"log","sha256":"adef5c","url":"s3://url"}]}'  -H "Accept: application/json" -H "Content-type: application/json"  http://localhost:3000/post
      ; {"task":1,"success":false,"files":[{"type":"log","sha256":"adef5c","url":"s3://url"}]}
    

    This is roundtripping on JSON->CLJ->JSON

    The json ring middleware expects the Content-type to be properly set so maybe that was why the structure was not parsed properly in your case ?