在 Clojure 中编码为 JSON 时如何表示非标准 java 对象?
How to represent non-standard java objects when encoding to JSON in Clojure?
我有一个标准的 clojure 事物映射。键是关键字,值是任意值。它们可以是 nil
、数字、字符串或任何其他类型的 JVM object/class.
我需要知道如何将此映射编码为 JSON,以便 "normal" 值映射到通常的 JSON 值(例如关键字 -> 字符串、整数 -> JSON 数字等),而任何其他 class 的值映射到这些值的字符串表示形式,如下所示:
{
: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]" }
我想这样做是因为我的程序是一个 CLI 解析库,我正在与库的调用者一起构建这个映射,所以我不完全知道里面会有什么类型的数据.但是,我还是想将它打印到屏幕上以帮助调用者进行调试。我试图天真地把这张地图交给柴郡,但当我这样做时,柴郡一直被这个错误所困扰:
Exception in thread "main" com.fasterxml.jackson.core.JsonGenerationException: Cannot JSON encode object of class: class java.io.File: foo
奖金:我正在努力减少我的依赖计数并且我已经审查了 cheshire 作为我的 JSON 库,但是如果你能找到一种没有它的方法来完成上述操作,则满分.
使用柴郡,您可以为 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"}
Cheshire 包括 Custom Encoders,您可以创建和注册以序列化任意 类。
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"]}
完全可读。
正如 Denis 所说,如果你想读回数据,你将需要做更多的工作,但无论如何这对于 File
对象这样的东西是不可能的。
如果您愿意,可以使用相关函数 pretty-str
以字符串形式获取结果(适用于 println
等):
(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\"]"}
您还可以覆盖您感兴趣的某些对象的 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"}
我有一个标准的 clojure 事物映射。键是关键字,值是任意值。它们可以是 nil
、数字、字符串或任何其他类型的 JVM object/class.
我需要知道如何将此映射编码为 JSON,以便 "normal" 值映射到通常的 JSON 值(例如关键字 -> 字符串、整数 -> JSON 数字等),而任何其他 class 的值映射到这些值的字符串表示形式,如下所示:
{
: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]" }
我想这样做是因为我的程序是一个 CLI 解析库,我正在与库的调用者一起构建这个映射,所以我不完全知道里面会有什么类型的数据.但是,我还是想将它打印到屏幕上以帮助调用者进行调试。我试图天真地把这张地图交给柴郡,但当我这样做时,柴郡一直被这个错误所困扰:
Exception in thread "main" com.fasterxml.jackson.core.JsonGenerationException: Cannot JSON encode object of class: class java.io.File: foo
奖金:我正在努力减少我的依赖计数并且我已经审查了 cheshire 作为我的 JSON 库,但是如果你能找到一种没有它的方法来完成上述操作,则满分.
使用柴郡,您可以为 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"}
Cheshire 包括 Custom Encoders,您可以创建和注册以序列化任意 类。
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"]}
完全可读。
正如 Denis 所说,如果你想读回数据,你将需要做更多的工作,但无论如何这对于 File
对象这样的东西是不可能的。
如果您愿意,可以使用相关函数 pretty-str
以字符串形式获取结果(适用于 println
等):
(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\"]"}
您还可以覆盖您感兴趣的某些对象的 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"}