Как представить нестандартные объекты java при кодировании в JSON в Clojure?
У меня есть стандартная карта закрытия. Ключи - это ключевые слова, а значения - произвольные значения. Они могут бытьnil
, числа, строки или любой другой объект / класс JVM.
Мне нужно знать, как кодировать эту карту в JSON, чтобы "нормальные" значения соответствовали обычным значениям JSON (например, ключевые слова -> строки, целые числа -> числа JSON и т. Д.), А значения любого другого класса сопоставляются с строковые представления этих значений, например:
{
:a 1
:b :myword
:c "hey"
:d <this is an "unprintable" java File object>
}
кодируется так:
{ "a": 1, "b": "myword", "c": "hey", "d": "#object[java.io.File 0x6944e53e foo]" }
Я хочу сделать это, потому что моя программа представляет собой библиотеку синтаксического анализа интерфейса командной строки, и я работаю с вызывающей стороной библиотеки, чтобы построить эту карту, поэтому я точно не знаю, какие типы данных будут в ней. Тем не менее, я все же хотел бы распечатать его на экране, чтобы помочь вызывающему абоненту в отладке. Я попытался наивно передать эту карту чеширу, но когда я это сделал, чешир продолжает задыхаться от этой ошибки:
Exception in thread "main" com.fasterxml.jackson.core.JsonGenerationException: Cannot JSON encode object of class: class java.io.File: foo
Бонус: я пытаюсь сохранить обратный отсчет моей зависимости, и я уже проверил чешир как свою библиотеку JSON, но полные оценки, если вы можете найти способ сделать это без него.
4 ответа
С помощью чешира вы можете добавить кодировщик для java.lang.Object
user> (require ['cheshire.core :as 'cheshire])
nil
user> (require ['cheshire.generate :as 'generate])
nil
user> (generate/add-encoder Object (fn [obj jsonGenerator] (.writeString jsonGenerator (str obj))))
nil
user> (def json (cheshire/generate-string {:a 1 :b nil :c "hello" :d (java.io.File. "/tmp")}))
#'user/json
user> (println json)
{"a":1,"b":null,"c":"hello","d":"/tmp"}
Вы также можете переопределить print-method
для некоторых интересующих вас объектов:
(defmethod print-method java.io.File [^java.io.File f ^java.io.Writer w]
(print-simple (str "\"File:" (.getCanonicalPath f) "\"") w))
метод вызывается подсистемой печати каждый раз, когда ему нужно распечатать объект этого типа:
user> {:a 10 :b (java.io.File. ".")}
;;=> {:a 10,
;; :b "File:/home/xxxx/dev/projects/clj"}
Cheshire включает специальные кодеры, которые вы можете создавать и регистрировать для сериализации произвольных классов.
OTOH, если вы хотите прочитать JSON обратно и воспроизвести те же типы на Java, вам также необходимо добавить некоторые метаданные. Распространенным шаблоном является кодирование типа как некоторого поля, например__type
или *class*
, вот так, чтобы десериализатор мог найти нужные типы:
{
__type: "org.foo.User"
name: "Jane Foo"
...
}
Если я чего-то не упускаю, здесь нет необходимости в JSON. Просто используйтеprn
:
(let [file (java.io.File. "/tmp/foo.txt")]
(prn {:a 1 :b "foo" :f file})
=> {:a 1,
:b "foo",
:f #object[java.io.File 0x5d617472 "/tmp/foo.txt"]}
Отлично читается.
Как сказал Денис, вам понадобится больше работы, если вы захотите прочитать данные, но это невозможно для таких вещей, как File
объект в любом случае.
Вы можете получить результат в виде строки (подходит для println
и т. д.) с помощью связанной функции pretty-str
Если вы предпочитаете:
(ns tst.demo.core
(:use tupelo.test)
(:require
[tupelo.core :as t] ))
(dotest
(let [file (java.io.File. "/tmp/foo.txt")]
(println (t/pretty-str {:a 1 :b "foo" :f file}))
))
=> {:a 1,
:b "foo",
:f #object[java.io.File 0x17d96ed9 "/tmp/foo.txt"]}
Обновить
Вот метод, который я часто использую, когда мне нужно преобразовать данные в другую форму, особенно. для отладки или юнит-тестов:
(ns tst.demo.core
(:use tupelo.core tupelo.test)
(:require
[clojure.walk :as walk]))
(defn walk-coerce-jsonable
[edn-data]
(let [coerce-jsonable (fn [item]
(cond
; coerce collections to simplest form
(sequential? item) (vec item)
(map? item) (into {} item)
(set? item) (into #{} item)
; coerce leaf values to String if can't JSON them
:else (try
(edn->json item)
item ; return item if no exception
(catch Exception ex
(pr-str item))))) ; if exception, return string version of item
result (walk/postwalk coerce-jsonable edn-data)]
result))
(dotest
(let [file (java.io.File. "/tmp/foo.txt")
m {:a 1 :b "foo" :f file}]
(println :converted (edn->json (walk-coerce-jsonable m)))
))
с результатом
-------------------------------
Clojure 1.10.1 Java 14
-------------------------------
Testing tst.demo.core
:converted {"a":1,"b":"foo","f":"#object[java.io.File 0x40429f12 \"/tmp/foo.txt\"]"}