поддержка ответов xml и json REST в clojure - PullRequest
6 голосов
/ 01 апреля 2012

Допустим, у меня есть API-интерфейс REST в Java, и он поддерживает ответы в формате JSON или XML. Ответы содержат те же данные, но форма не идентична. Например, в JSON я мог бы иметь:

{
    "persons": [
        {
            "name":"Bob",
            "age":24,
            "hometown":"New York"
        }
    ]
}

Тогда как в XML это выглядит так:

<persons>
    <person name="bob" age="24">
        <hometown>New York</hometown>
    </person>
</persons>

То есть некоторые значения являются атрибутами личности, а другие - дочерними элементами. В Java, используя JAXB и Джексона, легко скрыть такие различия с помощью аннотаций на объектах модели, например:

public class Person {
    @XmlAttribute
    String name;

    @XmlAttribute
    Integer age;

    @XmlElement 
    String hometown; 
}

JAXB читает аннотации, а Джексон использует имена полей, чтобы выяснить, что делать. Таким образом, с одной моделью легко поддерживать несколько форматов вывода.

Итак, мой вопрос, как сделать то же самое в clojure. Я знаю, что есть clj-json, который может легко конвертировать карты и векторы clojure в json (используя Джексона, если я не ошибаюсь). И я знаю, что есть и clojure.xml.emit, и clojure.contrib.xml.prxml, которые могут десериализовать карты и векторы в XML. Но если я не ошибаюсь, я не думаю, что эти два будут работать вместе очень хорошо.

Поскольку prxml ожидает, что узлы xml будут выражаться как векторы, а атрибуты xml - как карта, это принципиально отличается от работы clj-json, где векторы представляют массивы, а карты представляют объекты. И clojure.core.emit ожидает карту в форме {:tag :person :attrs {:name "Bob" :age 24} :content ...}, которая снова полностью отличается от того, что хочет clj-json.

Единственное, о чем я могу думать, это отформатировать структуры данных для prxml в моем коде, а затем написать функцию, которая преобразует структуру данных в то, что хочет clj-json, когда типом ответа является JSON. Но это кажется отстойным. Я бы предпочел, чтобы была пара библиотек JSON и XML, которые были бы совместимы так, как это делают JAXB и Джексон.

Идеи

1 Ответ

5 голосов
/ 02 апреля 2012

Многое зависит от того, как вы решите представлять модели в своем коде.

Предположим, вы используете записи. Вот надуманный пример того, как можно «аннотировать» запись и предоставлять сериализаторы для XML и JSON.

;; Depends on cheshire and data.xml
(ns user
  (:require [cheshire.core :as json]
            [clojure.data.xml :as xml]))

(defrecord Person [name age hometown])
(defrecord Animal [name sound])

(def xml-attrs {Person [:name :age]
                Animal [:name]})

(defn record->xml-data [rec]
  (let [tag (-> rec class .getSimpleName .toLowerCase keyword)
        attrs (select-keys rec (xml-attrs (class rec)))
        content (for [[k v] rec
                      :when (not (contains? attrs k))]
                  (xml/element k nil (str v)))]
    (apply xml/element tag attrs content)))

(defn record->xml [rec]
  (xml/emit-str (record->xml-data rec)))

(defn record->json [rec]
  (json/generate-string rec))

Использование:

> (def bob (Person. "Bob" 24 "New York"))
#'user/bob

> (println (record->xml bob))
<?xml version="1.0" encoding="UTF-8"?><person age="24" name="Bob"><hometown>New York</hometown></person>
nil

> (println (record->json bob))
{"name":"Bob","age":24,"hometown":"New York"}
nil

> (println (record->xml (Animal. "Fido" "Bark!")))
<?xml version="1.0" encoding="UTF-8"?><animal name="Fido"><sound>Bark!</sound></animal>
nil

Можно создать макрос для определения записи и ее атрибутов XML в отдельном операторе. Например.,

(defrecord-xml Person [^:xml-attr name ^:xml-attr age hometown])
...