yaji 0.3.5-x64-mingw32

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,84 @@
1
+ /*
2
+ * Copyright 2010, Lloyd Hilaiel.
3
+ *
4
+ * Redistribution and use in source and binary forms, with or without
5
+ * modification, are permitted provided that the following conditions are
6
+ * met:
7
+ *
8
+ * 1. Redistributions of source code must retain the above copyright
9
+ * notice, this list of conditions and the following disclaimer.
10
+ *
11
+ * 2. Redistributions in binary form must reproduce the above copyright
12
+ * notice, this list of conditions and the following disclaimer in
13
+ * the documentation and/or other materials provided with the
14
+ * distribution.
15
+ *
16
+ * 3. Neither the name of Lloyd Hilaiel nor the names of its
17
+ * contributors may be used to endorse or promote products derived
18
+ * from this software without specific prior written permission.
19
+ *
20
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
21
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
22
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23
+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
24
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
25
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
27
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
28
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
29
+ * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
30
+ * POSSIBILITY OF SUCH DAMAGE.
31
+ */
32
+
33
+ #ifndef __YAJL_PARSER_H__
34
+ #define __YAJL_PARSER_H__
35
+
36
+ #include "api/yajl_parse.h"
37
+ #include "yajl_bytestack.h"
38
+ #include "yajl_buf.h"
39
+
40
+
41
+ typedef enum {
42
+ yajl_state_start = 0,
43
+ yajl_state_parse_complete,
44
+ yajl_state_parse_error,
45
+ yajl_state_lexical_error,
46
+ yajl_state_map_start,
47
+ yajl_state_map_sep,
48
+ yajl_state_map_need_val,
49
+ yajl_state_map_got_val,
50
+ yajl_state_map_need_key,
51
+ yajl_state_array_start,
52
+ yajl_state_array_got_val,
53
+ yajl_state_array_need_val
54
+ } yajl_state;
55
+
56
+ struct yajl_handle_t {
57
+ const yajl_callbacks * callbacks;
58
+ void * ctx;
59
+ yajl_lexer lexer;
60
+ const char * parseError;
61
+ /* the number of bytes consumed from the last client buffer,
62
+ * in the case of an error this will be an error offset, in the
63
+ * case of an error this can be used as the error offset */
64
+ unsigned int bytesConsumed;
65
+ /* temporary storage for decoded strings */
66
+ yajl_buf decodeBuf;
67
+ /* a stack of states. access with yajl_state_XXX routines */
68
+ yajl_bytestack stateStack;
69
+ /* memory allocation routines */
70
+ yajl_alloc_funcs alloc;
71
+ };
72
+
73
+ YAJL_API
74
+ yajl_status
75
+ yajl_do_parse(yajl_handle handle, const unsigned char * jsonText,
76
+ unsigned int jsonTextLen);
77
+
78
+ YAJL_API
79
+ unsigned char *
80
+ yajl_render_error_string(yajl_handle hand, const unsigned char * jsonText,
81
+ unsigned int jsonTextLen, int verbose);
82
+
83
+
84
+ #endif
@@ -0,0 +1,7 @@
1
+ #include "api/yajl_version.h"
2
+
3
+ int yajl_version(void)
4
+ {
5
+ return YAJL_VERSION;
6
+ }
7
+
@@ -0,0 +1,24 @@
1
+ # encoding: utf-8
2
+ #
3
+ # Author:: Couchbase <info@couchbase.com>
4
+ # Copyright:: 2011 Couchbase, Inc.
5
+ # License:: Apache License, Version 2.0
6
+ #
7
+ # Licensed under the Apache License, Version 2.0 (the "License");
8
+ # you may not use this file except in compliance with the License.
9
+ # You may obtain a copy of the License at
10
+ #
11
+ # http://www.apache.org/licenses/LICENSE-2.0
12
+ #
13
+ # Unless required by applicable law or agreed to in writing, software
14
+ # distributed under the License is distributed on an "AS IS" BASIS,
15
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ # See the License for the specific language governing permissions and
17
+ # limitations under the License.
18
+ #
19
+
20
+ require "yaji/version"
21
+ require "parser_ext"
22
+
23
+ module YAJI
24
+ end
@@ -0,0 +1,22 @@
1
+ # encoding: utf-8
2
+ #
3
+ # Author:: Couchbase <info@couchbase.com>
4
+ # Copyright:: 2011 Couchbase, Inc.
5
+ # License:: Apache License, Version 2.0
6
+ #
7
+ # Licensed under the Apache License, Version 2.0 (the "License");
8
+ # you may not use this file except in compliance with the License.
9
+ # You may obtain a copy of the License at
10
+ #
11
+ # http://www.apache.org/licenses/LICENSE-2.0
12
+ #
13
+ # Unless required by applicable law or agreed to in writing, software
14
+ # distributed under the License is distributed on an "AS IS" BASIS,
15
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ # See the License for the specific language governing permissions and
17
+ # limitations under the License.
18
+ #
19
+
20
+ module YAJI
21
+ VERSION = "0.3.5"
22
+ end
@@ -0,0 +1,80 @@
1
+ gem 'rake-compiler', '>= 0.7.5'
2
+ require "rake/extensiontask"
3
+
4
+ def gemspec
5
+ @clean_gemspec ||= eval(File.read(File.expand_path('../../yaji.gemspec', __FILE__)))
6
+ end
7
+
8
+ require 'rubygems/package_task'
9
+ Gem::PackageTask.new(gemspec) do |pkg|
10
+ pkg.need_tar = true
11
+ end
12
+
13
+ version_router = lambda do |t|
14
+ File.open(t.name, 'wb') do |f|
15
+ f.write <<-RUBY
16
+ require "yaji/\#{RUBY_VERSION.sub(/\\.\\d+$/, '')}/parser_ext"
17
+ RUBY
18
+ end
19
+ end
20
+
21
+ class Platform
22
+ attr_reader :name, :host, :versions
23
+
24
+ def initialize(params)
25
+ @name = params[:name]
26
+ @host = params[:host]
27
+ @versions = params[:versions]
28
+ end
29
+
30
+ def each_version
31
+ @versions.each do |v|
32
+ yield(v, v[/\d\.\d\.\d/])
33
+ end
34
+ end
35
+
36
+ def short_versions
37
+ res = []
38
+ each_version do |long, short|
39
+ res << short
40
+ end
41
+ res
42
+ end
43
+ end
44
+
45
+ recent = "2.0.0-p353"
46
+ CROSS_PLATFORMS = [
47
+ Platform.new(:name => 'x64-mingw32', :host => 'x86_64-w64-mingw32', :versions => %w(1.9.3-p484 2.0.0-p353 2.1.0)),
48
+ Platform.new(:name => 'x86-mingw32', :host => 'i686-w64-mingw32', :versions => %w(1.8.7-p374 1.9.3-p484 2.0.0-p353 2.1.0)),
49
+ ]
50
+ Rake::ExtensionTask.new("parser_ext", gemspec) do |ext|
51
+ ext.ext_dir = File.join('ext', 'yaji')
52
+
53
+ ext.cross_compile = true
54
+ ext.cross_platform = ENV['TARGET']
55
+ if ENV['RUBY_CC_VERSION']
56
+ ext.lib_dir = "lib/yaji"
57
+ end
58
+ ext.cross_compiling do |spec|
59
+ spec.files.delete("lib/yaji/parser_ext.so")
60
+ spec.files.push("lib/parser_ext.rb", Dir["lib/yaji/*/parser_ext.so"])
61
+ file "#{ext.tmp_dir}/#{ext.cross_platform}/stage/lib/parser_ext.rb", &version_router
62
+ end
63
+
64
+ # clean compiled extension
65
+ CLEAN.include "#{ext.lib_dir}/*.#{RbConfig::CONFIG['DLEXT']}"
66
+ end
67
+
68
+ file "lib/parser_ext.rb", &version_router
69
+ task :cross => "lib/parser_ext.rb"
70
+
71
+ desc "Package gem for windows"
72
+ task "package:windows" => :package do
73
+ CROSS_PLATFORMS.each do |platform|
74
+ ENV['TARGET'] = platform.name
75
+ platform.each_version do |long, short|
76
+ sh("env RUBY_CC_VERSION=#{short} RBENV_VERSION=#{long} rbenv exec rake cross compile")
77
+ end
78
+ sh("env RUBY_CC_VERSION=#{platform.short_versions.join(":")} RBENV_VERSION=#{recent} rbenv exec rake cross native gem")
79
+ end
80
+ end
@@ -0,0 +1,7 @@
1
+ require 'rake/testtask'
2
+ Rake::TestTask.new do |test|
3
+ test.libs << "test" << "."
4
+ test.ruby_opts << "-rruby-debug" if ENV['DEBUG']
5
+ test.pattern = 'test/test_*.rb'
6
+ test.verbose = true
7
+ end
@@ -0,0 +1,4 @@
1
+ desc 'Start an irb session and load the library.'
2
+ task :console do
3
+ exec "irb -I lib -rruby-debug -ryaji"
4
+ end
@@ -0,0 +1,47 @@
1
+ {
2
+ "total_rows": 4,
3
+ "offset": 0,
4
+ "rows": [
5
+ {
6
+ "id": "3e8ceb5873cf91fa418c2ab5a0000386",
7
+ "key": "3e8ceb5873cf91fa418c2ab5a0000386",
8
+ "value": {
9
+ "_id": "3e8ceb5873cf91fa418c2ab5a0000386",
10
+ "_rev": "1-967a00dff5e02add41819138abb3284d"
11
+ }
12
+ },
13
+ {
14
+ "id": "biking",
15
+ "key": "biking",
16
+ "value": {
17
+ "_id": "biking",
18
+ "_rev": "2-d4b43fd051e693a5191db2eaba89903c",
19
+ "title": "Biking2",
20
+ "body": "My biggest hobby is mountainbiking. The other day...",
21
+ "date": "2009/01/30 18:04:11"
22
+ }
23
+ },
24
+ {
25
+ "id": "bought-a-cat",
26
+ "key": "bought-a-cat",
27
+ "value": {
28
+ "_id": "bought-a-cat",
29
+ "_rev": "1-ccd0a7e95ade7eb2d29cc02ffa582b30",
30
+ "title": "Bought a Cat",
31
+ "body": "I went to the the pet store earlier and brought home a little kitty...",
32
+ "date": "2009/02/17 21:13:39"
33
+ }
34
+ },
35
+ {
36
+ "id": "hello-world",
37
+ "key": "hello-world",
38
+ "value": {
39
+ "_id": "hello-world",
40
+ "_rev": "1-97dd85b06c25328a300f3f4041def370",
41
+ "title": "Hello World",
42
+ "body": "Well hello and welcome to my new blog...",
43
+ "date": "2009/01/15 15:52:20"
44
+ }
45
+ }
46
+ ]
47
+ }
@@ -0,0 +1,370 @@
1
+ require 'minitest/autorun'
2
+ require 'yaji'
3
+ require 'curb'
4
+
5
+ class TestParser < MiniTest::Unit::TestCase
6
+
7
+ class Generator
8
+ def initialize(data, options = {})
9
+ @callback = nil
10
+ @options = {:chunk_size => 10}.merge(options)
11
+ @chunks = case data
12
+ when Array
13
+ data
14
+ when String
15
+ count = (data.bytesize / @options[:chunk_size].to_f).ceil
16
+ data.unpack("a#{@options[:chunk_size]}" * count)
17
+ end
18
+ end
19
+
20
+ def on_body
21
+ old = @callback
22
+ if block_given?
23
+ @callback = Proc.new
24
+ end
25
+ old
26
+ end
27
+
28
+ def perform
29
+ size = @options[:chunk_size]
30
+ if @callback
31
+ @chunks.each do |chunk|
32
+ @callback.call(chunk)
33
+ end
34
+ end
35
+ end
36
+ end
37
+
38
+ def test_it_generates_events
39
+ events = []
40
+ parser = YAJI::Parser.new(toys_json_str)
41
+ parser.parse do |p, e, v|
42
+ events << [p, e, v]
43
+ end
44
+ expected = [
45
+ ["", :start_hash, nil],
46
+ ["", :hash_key, "total_rows"],
47
+ ["/total_rows", :number, 2],
48
+ ["", :hash_key, "rows"],
49
+ ["/rows", :start_array, nil],
50
+ ["/rows/", :start_hash, nil],
51
+ ["/rows/", :hash_key, "id"],
52
+ ["/rows//id", :string, "buzz"],
53
+ ["/rows/", :hash_key, "props"],
54
+ ["/rows//props", :start_hash, nil],
55
+ ["/rows//props", :hash_key, "humanoid"],
56
+ ["/rows//props/humanoid", :boolean, true],
57
+ ["/rows//props", :hash_key, "armed"],
58
+ ["/rows//props/armed", :boolean, true],
59
+ ["/rows//props", :end_hash, nil],
60
+ ["/rows/", :hash_key, "movies"],
61
+ ["/rows//movies", :start_array, nil],
62
+ ["/rows//movies/", :number, 1],
63
+ ["/rows//movies/", :number, 2],
64
+ ["/rows//movies/", :number, 3],
65
+ ["/rows//movies", :end_array, nil],
66
+ ["/rows/", :end_hash, nil],
67
+ ["/rows/", :start_hash, nil],
68
+ ["/rows/", :hash_key, "id"],
69
+ ["/rows//id", :string, "barbie"],
70
+ ["/rows/", :hash_key, "props"],
71
+ ["/rows//props", :start_hash, nil],
72
+ ["/rows//props", :hash_key, "humanoid"],
73
+ ["/rows//props/humanoid", :boolean, true],
74
+ ["/rows//props", :hash_key, "armed"],
75
+ ["/rows//props/armed", :boolean, false],
76
+ ["/rows//props", :end_hash, nil],
77
+ ["/rows/", :hash_key, "movies"],
78
+ ["/rows//movies", :start_array, nil],
79
+ ["/rows//movies/", :number, 2],
80
+ ["/rows//movies/", :number, 3],
81
+ ["/rows//movies", :end_array, nil],
82
+ ["/rows/", :end_hash, nil],
83
+ ["/rows", :end_array, nil],
84
+ ["", :end_hash, nil]
85
+ ]
86
+ assert_equal expected, events
87
+ end
88
+
89
+ def test_it_yields_enumerator
90
+ parser = YAJI::Parser.new('{"hello":"world"}')
91
+ e = parser.parse
92
+ assert_equal ["", :start_hash, nil], e.next
93
+ assert_equal ["", :hash_key, "hello"], e.next
94
+ assert_equal ["/hello", :string, "world"], e.next
95
+ assert_equal ["", :end_hash, nil], e.next
96
+ assert_raises(StopIteration) { e.next }
97
+ end
98
+
99
+ def test_it_symbolizes_keys
100
+ parser = YAJI::Parser.new('{"hello":"world"}', :symbolize_keys => true)
101
+ e = parser.parse
102
+ expected = [
103
+ ["", :start_hash, nil],
104
+ ["", :hash_key, :hello],
105
+ ["/hello", :string, "world"],
106
+ ["", :end_hash, nil]
107
+ ]
108
+ assert_equal expected, e.to_a
109
+ end
110
+
111
+ def test_it_build_ruby_objects
112
+ parser = YAJI::Parser.new(toys_json_str)
113
+ objects = []
114
+ parser.each do |o|
115
+ objects << o
116
+ end
117
+ expected = [{"total_rows" => 2,
118
+ "rows" => [
119
+ {
120
+ "id" => "buzz",
121
+ "props" => { "humanoid"=> true, "armed"=> true },
122
+ "movies" => [1,2,3]
123
+ },
124
+ {
125
+ "id" => "barbie",
126
+ "props" => { "humanoid"=> true, "armed"=> false },
127
+ "movies" => [2,3]
128
+ }
129
+ ]}]
130
+ assert_equal expected, objects
131
+ end
132
+
133
+ def test_it_yields_whole_array
134
+ parser = YAJI::Parser.new(toys_json_str)
135
+ objects = []
136
+ parser.each("/rows") do |o|
137
+ objects << o
138
+ end
139
+ expected = [[{
140
+ "id" => "buzz",
141
+ "props" => { "humanoid"=> true, "armed"=> true },
142
+ "movies" => [1,2,3]
143
+ },
144
+ {
145
+ "id" => "barbie",
146
+ "props" => { "humanoid"=> true, "armed"=> false },
147
+ "movies" => [2,3]
148
+ }]]
149
+ assert_equal expected, objects
150
+ end
151
+
152
+ def test_it_yeilds_array_contents_row_by_row
153
+ parser = YAJI::Parser.new(toys_json_str)
154
+ objects = []
155
+ parser.each("/rows/") do |o|
156
+ objects << o
157
+ end
158
+ expected = [{
159
+ "id" => "buzz",
160
+ "props" => { "humanoid"=> true, "armed"=> true },
161
+ "movies" => [1,2,3]
162
+ },
163
+ {
164
+ "id" => "barbie",
165
+ "props" => { "humanoid"=> true, "armed"=> false },
166
+ "movies" => [2,3]
167
+ }]
168
+ assert_equal expected, objects
169
+ end
170
+
171
+ def test_it_could_curb_async_approach
172
+ curl = Curl::Easy.new('http://avsej.net/test.json')
173
+ parser = YAJI::Parser.new(curl)
174
+ object = parser.each.to_a.first
175
+ expected = {"foo"=>"bar", "baz"=>{"nums"=>[42, 3.1415]}}
176
+ assert_equal expected, object
177
+ end
178
+
179
+ def test_it_allow_several_selectors
180
+ parser = YAJI::Parser.new(toys_json_str)
181
+ objects = []
182
+ parser.each(["/total_rows", "/rows/"]) do |o|
183
+ objects << o
184
+ end
185
+ expected = [2,
186
+ {
187
+ "id" => "buzz",
188
+ "props" => { "humanoid"=> true, "armed"=> true },
189
+ "movies" => [1,2,3]
190
+ },
191
+ {
192
+ "id" => "barbie",
193
+ "props" => { "humanoid"=> true, "armed"=> false },
194
+ "movies" => [2,3]
195
+ }]
196
+ assert_equal expected, objects
197
+ end
198
+
199
+ def test_it_optionally_yields_object_path
200
+ parser = YAJI::Parser.new(toys_json_str)
201
+ objects = []
202
+ parser.each(["/total_rows", "/rows/"], :with_path => true) do |o|
203
+ objects << o
204
+ end
205
+ expected = [["/total_rows", 2],
206
+ ["/rows/", {
207
+ "id" => "buzz",
208
+ "props" => { "humanoid"=> true, "armed"=> true },
209
+ "movies" => [1,2,3]
210
+ }],
211
+ ["/rows/", {
212
+ "id" => "barbie",
213
+ "props" => { "humanoid"=> true, "armed"=> false },
214
+ "movies" => [2,3]
215
+ }]]
216
+ assert_equal expected, objects
217
+ end
218
+
219
+ def test_it_allows_to_specify_filter_and_options_at_initialization
220
+ parser = YAJI::Parser.new(toys_json_str,
221
+ :filter => ["/total_rows", "/rows/"],
222
+ :with_path => true)
223
+ objects = []
224
+ parser.each do |o|
225
+ objects << o
226
+ end
227
+ expected = [["/total_rows", 2],
228
+ ["/rows/", {
229
+ "id" => "buzz",
230
+ "props" => { "humanoid"=> true, "armed"=> true },
231
+ "movies" => [1,2,3]
232
+ }],
233
+ ["/rows/", {
234
+ "id" => "barbie",
235
+ "props" => { "humanoid"=> true, "armed"=> false },
236
+ "movies" => [2,3]
237
+ }]]
238
+ assert_equal expected, objects
239
+ end
240
+
241
+ def test_it_doesnt_raise_exception_on_empty_input
242
+ YAJI::Parser.new("").parse
243
+ YAJI::Parser.new(" ").parse
244
+ YAJI::Parser.new("\n").parse
245
+ YAJI::Parser.new(" \n\n ").parse
246
+ end
247
+
248
+ def test_it_allows_to_create_parser_without_input
249
+ YAJI::Parser.new
250
+ YAJI::Parser.new(:filter => 'test')
251
+ YAJI::Parser.new(:with_path => true)
252
+ end
253
+
254
+ def test_it_raises_argument_error_for_parser_without_input
255
+ parser = YAJI::Parser.new
256
+ assert_raises(ArgumentError) do
257
+ parser.parse
258
+ end
259
+ assert_raises(ArgumentError) do
260
+ parser.each{|x| }
261
+ end
262
+ end
263
+
264
+ def test_it_raises_argument_error_on_write_without_callback_set_up
265
+ parser = YAJI::Parser.new
266
+ assert_raises(ArgumentError) do
267
+ parser.write('{"hello":"world"}')
268
+ end
269
+ end
270
+
271
+ def test_it_allows_to_feed_the_data_on_the_fly
272
+ parser = YAJI::Parser.new(:filter => '/rows/')
273
+
274
+ objects = []
275
+ parser.on_object do |obj|
276
+ objects << obj
277
+ end
278
+
279
+ parser.write(<<-JSON)
280
+ {
281
+ "total_rows": 2,
282
+ "rows": [
283
+ {
284
+ JSON
285
+ parser.write(<<-JSON)
286
+ "id": "buzz",
287
+ "props": {
288
+ "humanoid": true,
289
+ "armed": true
290
+ },
291
+ "movies": [1,2,3]
292
+ },
293
+ JSON
294
+ data = <<-JSON
295
+ {
296
+ "id": "barbie",
297
+ "props": {
298
+ "humanoid": true,
299
+ "armed": false
300
+ },
301
+ "movies": [2,3]
302
+ }
303
+ ]
304
+ }
305
+ JSON
306
+ parser << data
307
+
308
+ expected = [{
309
+ "id" => "buzz",
310
+ "props" => { "humanoid"=> true, "armed"=> true },
311
+ "movies" => [1,2,3]
312
+ },
313
+ {
314
+ "id" => "barbie",
315
+ "props" => { "humanoid"=> true, "armed"=> false },
316
+ "movies" => [2,3]
317
+ }]
318
+ assert_equal expected, objects
319
+ end
320
+
321
+ def test_it_parses_chunked_data
322
+ generator = Generator.new(['{"total_rows":', '0,"offset":0,"rows":[]', '}'])
323
+ iter = YAJI::Parser.new(generator).each(["total_rows", "/rows/", "/errors/"], :with_path => true)
324
+ begin
325
+ loop do
326
+ iter.next
327
+ end
328
+ rescue StopIteration
329
+ end
330
+ end
331
+
332
+ def test_it_skips_empty_chunks
333
+ generator = Generator.new(['{"total_rows":', '0,"offset":0,"rows":[]', '}', '', nil])
334
+ iter = YAJI::Parser.new(generator).each(["total_rows", "/rows/", "/errors/"], :with_path => true)
335
+ begin
336
+ loop do
337
+ iter.next
338
+ end
339
+ rescue StopIteration
340
+ end
341
+ end
342
+
343
+ protected
344
+
345
+ def toys_json_str
346
+ <<-JSON
347
+ {
348
+ "total_rows": 2,
349
+ "rows": [
350
+ {
351
+ "id": "buzz",
352
+ "props": {
353
+ "humanoid": true,
354
+ "armed": true
355
+ },
356
+ "movies": [1,2,3]
357
+ },
358
+ {
359
+ "id": "barbie",
360
+ "props": {
361
+ "humanoid": true,
362
+ "armed": false
363
+ },
364
+ "movies": [2,3]
365
+ }
366
+ ]
367
+ }
368
+ JSON
369
+ end
370
+ end