Статьи

Groovy 1.8.0 — познакомьтесь с JsonBuilder!

В выпущенном в апреле Groovy 1.8.0 появилось много новых функций, одна из которых — встроенная поддержка JSON через JsonSlurper для чтения JSON и JsonBuilder для написания JSON.

Недавно я использовал JsonBuilder в одном из моих проектов и изначально испытывал некоторые трудности в понимании того, как он работает. Я предположил, что JsonBuilder работает аналогично MarkupBuilder, но, как я быстро выяснил, на самом деле это не так.

Давайте возьмем простой пример. Предположим, у нас есть класс Message, который мы хотим сериализовать в разметку XML и JSON.

1
2
3
4
5
6
7
8
9
@groovy.transform.Canonical
class Message {
    long   id
    String sender
    String text
}
  
assert 'Message(23, me, some text)' ==
       new Message( 23, 'me', 'some text' ).toString()

Здесь я использовал Groovy 1.8.0 @Canonical аннотацию, обеспечивающую автоматическое toString () , equals () и hashCode () и конструктор кортежа (упорядоченный) .

Давайте сериализуем ряд сообщений в XML.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
def messages = [ new Message( 23, 'me', 'some text'       ),
                 new Message( 24, 'me', 'some other text' ),
                 new Message( 25, 'me', 'same text'       )]
  
def writer = new StringWriter()
def xml    = new groovy.xml.MarkupBuilder( writer )
  
xml.messages() {
    messages.each { Message m -> message( id     : m.id,
                                          sender : m.sender,
                                          text   : m.text )}
}
  
assert writer.toString() == """
<messages>
  <message id='23' sender='me' text='some text' />
  <message id='24' sender='me' text='some other text' />
  <message id='25' sender='me' text='same text' />
</messages>""".trim()

Ну, это было довольно просто. Давайте попробуем сделать то же самое с JSON.

01
02
03
04
05
06
07
08
09
10
def json = new groovy.json.JsonBuilder()
  
json.messages() {
    messages.each { Message m -> message( id     : m.id,
                                          sender : m.sender,
                                          text   : m.text )}
}
  
assert json.toString() ==
       '{"messages":{"message":{"id":25,"sender":"me","text":"same text"}}}'

Вау, куда делись все остальные сообщения? Почему только одно последнее сообщение в списке было сериализовано?
Как насчет этого:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
json = new groovy.json.JsonBuilder()
  
json.messages() {
    message {
        id     23
        sender 'me'
        text   'some text'
    }
    message {
        id     24
        sender 'me'
        text   'some other text'
    }
}
  
assert json.toString() ==
       '{"messages":{"message":{"id":24,"sender":"me","text":"some other text"}}}'

Та же история. Сначала я был озадачен, но затем исходный код JsonBuilder показал, что каждый вызов переопределяет предыдущий контент:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
JsonBuilder(content = null) {
    this.content = content
}
  
def call(Map m) {
    this.content = m
    return content
}
  
def call(List l) {
    this.content = l
    return content
}
  
def call(Object... args) {
    this.content = args.toList()
    return this.content
}
  
def call(Closure c) {
    this.content = JsonDelegate.cloneDelegateAndGetContent(c)
    return content
}

Как видите, нужно вызвать JsonBuilder ровно один раз, передав ему Map , List , varargs или Closure . Это сильно отличает JsonBuilder от MarkupBuilder, который можно обновлять столько раз, сколько необходимо. Это может быть вызвано самим JSON, формат которого более строг, чем разметка XML свободной формы: то, что начиналось как карта JSON с одним Сообщением , не может быть внезапно превращено в массив Сообщений .

Аргумент, передаваемый в JsonBuilder (Map, List, varargs или Closure), также может быть указан в конструкторе, поэтому вообще не нужно вызывать конструктор. Вы можете просто инициализировать его с соответствующей структурой данных и сразу вызвать toString () . Давайте попробуем это!

01
02
03
04
05
06
07
08
09
10
def listOfMaps = messages.collect{
                 Message m -> [ id     : m.id,
                                sender : m.sender,
                                text   : m.text ]}
  
assert new groovy.json.JsonBuilder( listOfMaps ).toString() ==
       '''[{"id":23,"sender":"me","text":"some text"},
           {"id":24,"sender":"me","text":"some other text"},
           {"id":25,"sender":"me","text":"same text"}]'''.
       readLines()*.trim().join()

Теперь это работает 🙂 После преобразования списка сообщений в список Карт и отправки их в JsonBuilder за один раз, сгенерированная строка содержит все сообщения из списка. Весь приведенный выше код доступен в веб-консоли Groovy, поэтому вы можете попробовать его.

Кстати, для просмотра онлайн JSON я рекомендую отличное приложение « JSON Visualization », созданное Крисом Нильсеном. « Online JSON Viewer » — еще один популярный вариант, но я предпочитаю первый. А для автономного использования « JSON Viewer » делает хороший плагин Fiddler .

PS
Если вам нужно прочитать этот JSON на стороне клиента, отправив, скажем, Ajax GET-запрос, это легко сделать с помощью jQuery.get () :

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
<script type="text/javascript">
var j = jQuery;
  
j( function() {
    j.get( 'url',
           { timestamp: new Date().getTime() },
           function ( messages ){
               j.each( messages, function( index, m ) {
                   alert( "[" + m.id + "][" + m.sender + "][" + m.text + "]" );
               });
           },
           'json'
        );
});
</script>

Здесь я использую хитрый прием ярлыка j, чтобы не вводить слишком много раз jQuery, когда использование $ не является опцией.

Ссылка: Groovy 1.8.0 — познакомьтесь с JsonBuilder! от нашего партнера JCG