Как преобразовать JSON в EDN в Clojure?

Я думаю, что это простой вопрос, но как новичок в Clojure, я хотел бы преобразовать простой JSON в EDN в Clojure.

Мой JSON:

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

Итак, если мы предположим, что data - это json, указанный выше, я попытался преобразовать его в EDN, используя Cheshire следующим образом:

(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?

Заранее спасибо за вашу помощь


person Jett    schedule 16.07.2020    source источник
comment
Если вы утверждаете, что эти данные представляют собой приведенный выше JSON, вы, скорее всего, имеете в виду строку. Или, если нет, вы имеете в виду уже десериализованные данные - в любом случае вы, скорее всего, не хотите вызывать данные - паренсы в clojure никогда не для развлечения. использование результата данных в качестве ключа к карте тоже выглядит странно.   -  person cfrick    schedule 16.07.2020


Ответы (3)


Вы получаете эту ошибку для этой строки:

(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)))
person cfrick    schedule 16.07.2020
comment
Обратите внимание, что pr-str будет ограничивать свой вывод на основе значения *print-length* и, возможно, других динамически связанных переменных. Поэтому, если у вас длинные последовательности, вы можете получить испорченный вывод edn. - person Rulle; 16.07.2020
comment
@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))) должно хватить, чтобы избежать проблем - person leetwinski; 16.07.2020

Это поможет прояснить, что происходит. Начнем с добавления полезной библиотеки:

(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 в структуру данных EDN (а не строку). Затем мы преобразуем структуру данных EDN в строку EDN. Вывод (как println, так и prn) иллюстрирует различия:

(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}}"

Тщательно продумайте, что такое структура данных, а что - строка. Если мы напишем [1 :b "hi"] в исходном файле Clojure, Clojure Reader создаст структуру данных, которая представляет собой трехэлементный вектор, содержащий int, ключевое слово и строку. Если мы преобразуем это в строку с помощью str, на выходе будет строка из 11 символов, а не структура данных.

Однако, если мы хотим вставить эту строку (внутри двойных кавычек) в наш исходный файл, нам нужны не только внешние двойные кавычки для обозначения начала и конца строки, но и каждая двойная кавычка внутри строки (например, как часть "hi") должны быть экранированы, чтобы Clojure Reader мог сказать, что они принадлежат внутри строки, и не отмечают начало или конец строки.

Чтобы привыкнуть ко всем режимам, нужно время!


Clojure Reader против компилятора

Файлы исходного кода Clojure обрабатываются за 2 прохода.

  1. Clojure Reader берет текст каждого исходного файла (гигантскую строку из многих строк) и преобразует его в структуру данных.

  2. Компилятор Clojure берет структуру данных из (1) и выводит байт-код Java.

person Alan Thompson    schedule 16.07.2020

Вы получаете эту ошибку, потому что вы передаете данные переменных как карту clojure, а не как строку (или объект json).

Об этом же и говорится в ошибке.

cheshire.core / parse-string ожидает объект json, который будет строкой.

'{ "name": "Cheshire", "needs": "a string"}'
person Bijur Vallark    schedule 16.07.2020