【问题标题】:How to convert JSON to EDN in Clojure?如何在 Clojure 中将 JSON 转换为 EDN?
【发布时间】:2020-11-05 18:32:25
【问题描述】:

我认为这是一个简单的问题,但作为 Clojure 的初学者,我想在 Clojure 中将简单的 JSON 转换为 EDN。

我的 JSON:

{
    "Data": [
        {
            "Metadata": {
                "Series": "1/2"
            },
            "Hybrid": {
                "Foo": 76308,
                "Bar": "76308",
                "Cat": "Foo123"
            }
        }
    ],
    "Footer": {
        "Count": 3,
        "Age": 0
    }
}

因此,如果我们假设 data 是上面的 json,我尝试使用 Cheshire 将其转换为 EDN,如下所示:

(log/info "data" (cheshire.core/parse-string {(data) true}))

但是,每当我运行这段代码时,我都会收到错误消息:

ERROR compojure.api.exception - clojure.lang.PersistentArrayMap cannot be cast to java.lang.String

我想我得到这个是因为我的 JSON 不是字符串,但不确定我是否首先需要将其转换为字符串,然后 然后 转换为 EDN - 或者我是否有办法可以直接从 JSON 转到 EDN 吗?

提前感谢您的帮助

【问题讨论】:

  • 如果您说“数据”是上面的 JSON,那么您很可能是指字符串。或者,如果不是,您指的是已经反序列化的数据 - 无论如何,您很可能不想调用数据 - clojure 中的括号绝不是为了好玩。使用数据的结果然后作为地图的关键看起来也很奇怪。

标签: json clojure type-conversion edn


【解决方案1】:

您收到此行的此错误:

(cheshire.core/parse-string {(data) true})

这里发生了什么:

  1. 无论您的数据是什么,它似乎都可以调用 - 否则 (data) 已经失败了。例如。 ("{}") 会给你一个 class java.lang.String cannot be cast to class clojure.lang.IFn 错误。 同样不太可能,data 已经是反序列化的地图,因为 地图是函数,但至少有一个。所以我们有 假设data 实际上是一个函数并返回任何内容。
  2. 接下来,这个调用的结果被放入一个新的映射中,作为键和 值true ({? true})。
  3. 然后这张地图传给了柴郡的parse-string,但是这个 显然不是字符串,而是地图,因此出现错误。

鉴于所有这些,并假设 (data) 返回一个 String,您的 最好的选择是:

(cheshire.core/parse-string (data))

如果你真的想要 EDN,那就是:

(pr-str (cheshire.core/parse-string (data)))

【讨论】:

  • 请注意,pr-str 将根据*print-length* 的值以及其他动态绑定的变量来限制其输出。所以如果你有很长的序列,你最终可能会得到损坏的 edn 输出。
  • @Rulle 是对的。不仅*print-length*,还有更多。我想(defn edn-str [data] (binding [*print-meta* false *print-level* nil *print-namespace-maps* false *print-length* nil *print-readably* true *print-dup* false] (pr-str data))) 应该足以避免问题
【解决方案2】:

这将有助于澄清正在发生的事情。我们首先包含一个有用的库:

(ns tst.demo.core
  (:use tupelo.core tupelo.test)
  (:require
    [tupelo.core :as t]
    [tupelo.string :as str]
    ))

为了避免文本字符串和嵌入字符串中的双引号的歧义,我们使用单引号编写源 JSON,然后使用辅助函数 str/quotes->double 来转换字符串中的每个单引号字符串成双引号。否则,我们可以从文件中读取源 JSON(而不是内联)。

(def json-str
  (str/quotes->double
    "{
      'Data': [
               {
                'Metadata': {
                             'Series': '1/2'
                            },
                'Hybrid': {
                           'Foo': 76308,
                           'Bar': '76308',
                           'Cat': 'Foo123'
                          }
               }
              ],
      'Footer': {
                 'Count': 3,
                 'Age': 0
                }
     } "))

我们首先将 json string 转换为 EDN 数据结构(不是字符串)。然后我们将 EDN 数据结构转换为 EDN 字符串。输出(printlnprn)说明了差异:

(dotest
  (let [edn-data (t/json->edn json-str) ; JSON string => EDN data
        edn-str  (pr-str edn-data) ; EDN data => EDN string
        ]
    (newline)
    (println "edn-data =>")
    (spy-pretty edn-data)     ; uses 'prn'

    (newline)
    (println "edn-str (println) =>")
    (println edn-str)

    (newline)
    (println "edn-str (prn) =>")
    (prn edn-str)))

结果:

------------------------------------------
   Clojure 1.10.2-alpha1    Java 14.0.1
------------------------------------------

Testing tst.demo.core

edn-data =>
{:Data
 [{:Metadata {:Series "1/2"},
   :Hybrid {:Foo 76308, :Bar "76308", :Cat "Foo123"}}],
 :Footer {:Count 3, :Age 0}}

edn-str (println) =>
{:Data [{:Metadata {:Series "1/2"}, :Hybrid {:Foo 76308, :Bar "76308", :Cat "Foo123"}}], :Footer {:Count 3, :Age 0}}

edn-str (prn) =>
"{:Data [{:Metadata {:Series \"1/2\"}, :Hybrid {:Foo 76308, :Bar \"76308\", :Cat \"Foo123\"}}], :Footer {:Count 3, :Age 0}}"

仔细思考什么是数据结构,什么是字符串。如果我们在 Clojure 源文件中写入 [1 :b "hi"],Clojure Reader 会创建一个数据结构,它是一个包含 int、关键字和字符串的 3 元素向量。如果我们使用str将其转换为字符串,则输出只是一个11个字符的字符串,而不是数据结构。

但是,如果我们想将该字符串(在双引号内)粘贴到源文件中,我们不仅需要外部双引号来标记字符串的开头和结尾,还需要每个双引号内的字符串(例如,作为"hi" 的一部分)需要转义,因此Clojure 阅读器可以判断它们属于字符串内部并且不标记字符串的开头或结尾。

适应所有不同的模式需要一段时间!


Clojure 阅读器与编译器

Clojure 源代码文件分两遍处理。

  1. Clojure Reader 获取每个源文件的文本(多行的巨大字符串)并将其转换为数据结构。

  2. Clojure 编译器从 (1) 中获取数据结构并输出 Java 字节码。

【讨论】:

    【解决方案3】:

    您收到此错误是因为您将变量数据作为 clojure Map 而不是作为字符串(或 json 对象)传递。

    这实际上也是错误所说的。

    cheshire.core/parse-string 需要一个 json 对象,该对象将是一个字符串。

    '{ "name": "Cheshire", "needs": "a string"}'
    

    【讨论】:

      猜你喜欢
      • 2021-05-07
      • 1970-01-01
      • 1970-01-01
      • 2021-05-07
      • 2020-04-19
      • 2020-06-04
      • 1970-01-01
      • 1970-01-01
      • 2013-11-14
      相关资源
      最近更新 更多