json_pure 1.0.0 → 1.4.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/CHANGES +155 -1
- data/COPYING +58 -0
- data/GPL +7 -7
- data/README +324 -45
- data/Rakefile +166 -124
- data/TODO +1 -1
- data/VERSION +1 -1
- data/benchmarks/data-p4-3GHz-ruby18/GeneratorBenchmarkComparison.log +52 -0
- data/benchmarks/data-p4-3GHz-ruby18/GeneratorBenchmarkExt#generator_fast-autocorrelation.dat +1000 -0
- data/benchmarks/data-p4-3GHz-ruby18/GeneratorBenchmarkExt#generator_fast.dat +1001 -0
- data/benchmarks/data-p4-3GHz-ruby18/GeneratorBenchmarkExt#generator_pretty-autocorrelation.dat +900 -0
- data/benchmarks/data-p4-3GHz-ruby18/GeneratorBenchmarkExt#generator_pretty.dat +901 -0
- data/benchmarks/data-p4-3GHz-ruby18/GeneratorBenchmarkExt#generator_safe-autocorrelation.dat +1000 -0
- data/benchmarks/data-p4-3GHz-ruby18/GeneratorBenchmarkExt#generator_safe.dat +1001 -0
- data/benchmarks/data-p4-3GHz-ruby18/GeneratorBenchmarkExt.log +261 -0
- data/benchmarks/data-p4-3GHz-ruby18/GeneratorBenchmarkPure#generator_fast-autocorrelation.dat +1000 -0
- data/benchmarks/data-p4-3GHz-ruby18/GeneratorBenchmarkPure#generator_fast.dat +1001 -0
- data/benchmarks/data-p4-3GHz-ruby18/GeneratorBenchmarkPure#generator_pretty-autocorrelation.dat +1000 -0
- data/benchmarks/data-p4-3GHz-ruby18/GeneratorBenchmarkPure#generator_pretty.dat +1001 -0
- data/benchmarks/data-p4-3GHz-ruby18/GeneratorBenchmarkPure#generator_safe-autocorrelation.dat +1000 -0
- data/benchmarks/data-p4-3GHz-ruby18/GeneratorBenchmarkPure#generator_safe.dat +1001 -0
- data/benchmarks/data-p4-3GHz-ruby18/GeneratorBenchmarkPure.log +262 -0
- data/benchmarks/data-p4-3GHz-ruby18/GeneratorBenchmarkRails#generator-autocorrelation.dat +1000 -0
- data/benchmarks/data-p4-3GHz-ruby18/GeneratorBenchmarkRails#generator.dat +1001 -0
- data/benchmarks/data-p4-3GHz-ruby18/GeneratorBenchmarkRails.log +82 -0
- data/benchmarks/data-p4-3GHz-ruby18/ParserBenchmarkComparison.log +34 -0
- data/benchmarks/data-p4-3GHz-ruby18/ParserBenchmarkExt#parser-autocorrelation.dat +900 -0
- data/benchmarks/data-p4-3GHz-ruby18/ParserBenchmarkExt#parser.dat +901 -0
- data/benchmarks/data-p4-3GHz-ruby18/ParserBenchmarkExt.log +81 -0
- data/benchmarks/data-p4-3GHz-ruby18/ParserBenchmarkPure#parser-autocorrelation.dat +1000 -0
- data/benchmarks/data-p4-3GHz-ruby18/ParserBenchmarkPure#parser.dat +1001 -0
- data/benchmarks/data-p4-3GHz-ruby18/ParserBenchmarkPure.log +82 -0
- data/benchmarks/data-p4-3GHz-ruby18/ParserBenchmarkRails#parser-autocorrelation.dat +1000 -0
- data/benchmarks/data-p4-3GHz-ruby18/ParserBenchmarkRails#parser.dat +1001 -0
- data/benchmarks/data-p4-3GHz-ruby18/ParserBenchmarkRails.log +82 -0
- data/benchmarks/data-p4-3GHz-ruby18/ParserBenchmarkYAML#parser-autocorrelation.dat +1000 -0
- data/benchmarks/data-p4-3GHz-ruby18/ParserBenchmarkYAML#parser.dat +1001 -0
- data/benchmarks/data-p4-3GHz-ruby18/ParserBenchmarkYAML.log +82 -0
- data/benchmarks/generator2_benchmark.rb +222 -0
- data/benchmarks/generator_benchmark.rb +224 -0
- data/benchmarks/ohai.json +1216 -0
- data/benchmarks/ohai.ruby +1 -0
- data/benchmarks/parser2_benchmark.rb +251 -0
- data/benchmarks/parser_benchmark.rb +259 -0
- data/bin/edit_json.rb +1 -3
- data/bin/prettify_json.rb +75 -0
- data/data/index.html +5 -4
- data/data/prototype.js +2764 -1095
- data/ext/json/ext/generator/extconf.rb +14 -3
- data/ext/json/ext/generator/generator.c +1022 -334
- data/ext/json/ext/generator/generator.h +197 -0
- data/ext/json/ext/parser/extconf.rb +9 -3
- data/ext/json/ext/parser/parser.c +961 -577
- data/ext/json/ext/parser/parser.h +71 -0
- data/ext/json/ext/parser/parser.rl +400 -123
- data/install.rb +0 -0
- data/lib/json/add/core.rb +148 -0
- data/lib/json/add/rails.rb +58 -0
- data/lib/json/common.rb +254 -47
- data/lib/json/editor.rb +236 -72
- data/lib/json/ext.rb +2 -0
- data/lib/json/pure/generator.rb +235 -117
- data/lib/json/pure/parser.rb +124 -25
- data/lib/json/pure.rb +5 -3
- data/lib/json/version.rb +1 -1
- data/lib/json.rb +2 -197
- data/tests/fixtures/fail18.json +1 -0
- data/tests/test_json.rb +181 -22
- data/tests/test_json_addition.rb +84 -16
- data/tests/test_json_encoding.rb +68 -0
- data/tests/test_json_fixtures.rb +9 -5
- data/tests/test_json_generate.rb +114 -14
- data/tests/test_json_rails.rb +144 -0
- data/tests/test_json_unicode.rb +35 -14
- data/tools/fuzz.rb +13 -7
- data/tools/server.rb +0 -1
- metadata +156 -122
- data/benchmarks/benchmark.txt +0 -133
- data/benchmarks/benchmark_generator.rb +0 -44
- data/benchmarks/benchmark_parser.rb +0 -22
- data/benchmarks/benchmark_rails.rb +0 -26
- data/ext/json/ext/generator/Makefile +0 -149
- data/ext/json/ext/generator/unicode.c +0 -184
- data/ext/json/ext/generator/unicode.h +0 -40
- data/ext/json/ext/parser/Makefile +0 -149
- data/ext/json/ext/parser/unicode.c +0 -156
- data/ext/json/ext/parser/unicode.h +0 -44
- data/tests/fixtures/pass18.json +0 -1
- data/tests/runner.rb +0 -24
- /data/tests/fixtures/{fail15.json → pass15.json} +0 -0
- /data/tests/fixtures/{fail16.json → pass16.json} +0 -0
- /data/tests/fixtures/{fail17.json → pass17.json} +0 -0
- /data/tests/fixtures/{fail26.json → pass26.json} +0 -0
data/lib/json/pure/generator.rb
CHANGED
|
@@ -34,30 +34,67 @@ module JSON
|
|
|
34
34
|
"\x1f" => '\u001f',
|
|
35
35
|
'"' => '\"',
|
|
36
36
|
'\\' => '\\\\',
|
|
37
|
-
'/' => '\/',
|
|
38
37
|
} # :nodoc:
|
|
39
38
|
|
|
40
39
|
# Convert a UTF8 encoded Ruby string _string_ to a JSON string, encoded with
|
|
41
40
|
# UTF16 big endian characters as \u????, and return it.
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
41
|
+
if defined?(::Encoding)
|
|
42
|
+
def utf8_to_json(string) # :nodoc:
|
|
43
|
+
string = string.dup
|
|
44
|
+
string << '' # XXX workaround: avoid buffer sharing
|
|
45
|
+
string.force_encoding(::Encoding::ASCII_8BIT)
|
|
46
|
+
string.gsub!(/["\\\x0-\x1f]/) { MAP[$&] }
|
|
47
|
+
string.force_encoding(::Encoding::UTF_8)
|
|
48
|
+
string
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def utf8_to_json_ascii(string) # :nodoc:
|
|
52
|
+
string = string.dup
|
|
53
|
+
string << '' # XXX workaround: avoid buffer sharing
|
|
54
|
+
string.force_encoding(::Encoding::ASCII_8BIT)
|
|
55
|
+
string.gsub!(/["\\\x0-\x1f]/) { MAP[$&] }
|
|
56
|
+
string.gsub!(/(
|
|
57
|
+
(?:
|
|
58
|
+
[\xc2-\xdf][\x80-\xbf] |
|
|
59
|
+
[\xe0-\xef][\x80-\xbf]{2} |
|
|
60
|
+
[\xf0-\xf4][\x80-\xbf]{3}
|
|
61
|
+
)+ |
|
|
62
|
+
[\x80-\xc1\xf5-\xff] # invalid
|
|
63
|
+
)/nx) { |c|
|
|
64
|
+
c.size == 1 and raise GeneratorError, "invalid utf8 byte: '#{c}'"
|
|
65
|
+
s = JSON::UTF8toUTF16.iconv(c).unpack('H*')[0]
|
|
66
|
+
s.gsub!(/.{4}/n, '\\\\u\&')
|
|
67
|
+
}
|
|
68
|
+
string.force_encoding(::Encoding::UTF_8)
|
|
69
|
+
string
|
|
70
|
+
rescue Iconv::Failure => e
|
|
71
|
+
raise GeneratorError, "Caught #{e.class}: #{e}"
|
|
72
|
+
end
|
|
73
|
+
else
|
|
74
|
+
def utf8_to_json(string) # :nodoc:
|
|
75
|
+
string.gsub(/["\\\x0-\x1f]/) { MAP[$&] }
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def utf8_to_json_ascii(string) # :nodoc:
|
|
79
|
+
string = string.gsub(/["\\\x0-\x1f]/) { MAP[$&] }
|
|
80
|
+
string.gsub!(/(
|
|
81
|
+
(?:
|
|
82
|
+
[\xc2-\xdf][\x80-\xbf] |
|
|
83
|
+
[\xe0-\xef][\x80-\xbf]{2} |
|
|
84
|
+
[\xf0-\xf4][\x80-\xbf]{3}
|
|
85
|
+
)+ |
|
|
86
|
+
[\x80-\xc1\xf5-\xff] # invalid
|
|
87
|
+
)/nx) { |c|
|
|
88
|
+
c.size == 1 and raise GeneratorError, "invalid utf8 byte: '#{c}'"
|
|
89
|
+
s = JSON::UTF8toUTF16.iconv(c).unpack('H*')[0]
|
|
90
|
+
s.gsub!(/.{4}/n, '\\\\u\&')
|
|
91
|
+
}
|
|
92
|
+
string
|
|
93
|
+
rescue Iconv::Failure => e
|
|
94
|
+
raise GeneratorError, "Caught #{e.class}: #{e}"
|
|
95
|
+
end
|
|
59
96
|
end
|
|
60
|
-
module_function :utf8_to_json
|
|
97
|
+
module_function :utf8_to_json, :utf8_to_json_ascii
|
|
61
98
|
|
|
62
99
|
module Pure
|
|
63
100
|
module Generator
|
|
@@ -75,7 +112,7 @@ module JSON
|
|
|
75
112
|
when Hash
|
|
76
113
|
new(opts)
|
|
77
114
|
else
|
|
78
|
-
|
|
115
|
+
SAFE_STATE_PROTOTYPE.dup
|
|
79
116
|
end
|
|
80
117
|
end
|
|
81
118
|
|
|
@@ -88,16 +125,21 @@ module JSON
|
|
|
88
125
|
# * *space_before*: a string that is put before a : pair delimiter (default: ''),
|
|
89
126
|
# * *object_nl*: a string that is put at the end of a JSON object (default: ''),
|
|
90
127
|
# * *array_nl*: a string that is put at the end of a JSON array (default: ''),
|
|
91
|
-
# * *check_circular*:
|
|
92
|
-
#
|
|
128
|
+
# * *check_circular*: is deprecated now, use the :max_nesting option instead,
|
|
129
|
+
# * *max_nesting*: sets the maximum level of data structure nesting in
|
|
130
|
+
# the generated JSON, max_nesting = 0 if no maximum should be checked.
|
|
131
|
+
# * *allow_nan*: true if NaN, Infinity, and -Infinity should be
|
|
132
|
+
# generated, otherwise an exception is thrown, if these values are
|
|
133
|
+
# encountered. This options defaults to false.
|
|
93
134
|
def initialize(opts = {})
|
|
94
|
-
@indent =
|
|
95
|
-
@space =
|
|
96
|
-
@space_before =
|
|
97
|
-
@object_nl =
|
|
98
|
-
@array_nl =
|
|
99
|
-
@
|
|
100
|
-
@
|
|
135
|
+
@indent = ''
|
|
136
|
+
@space = ''
|
|
137
|
+
@space_before = ''
|
|
138
|
+
@object_nl = ''
|
|
139
|
+
@array_nl = ''
|
|
140
|
+
@allow_nan = false
|
|
141
|
+
@ascii_only = false
|
|
142
|
+
configure opts
|
|
101
143
|
end
|
|
102
144
|
|
|
103
145
|
# This string is used to indent levels in the JSON text.
|
|
@@ -117,27 +159,82 @@ module JSON
|
|
|
117
159
|
# This string is put at the end of a line that holds a JSON array.
|
|
118
160
|
attr_accessor :array_nl
|
|
119
161
|
|
|
120
|
-
#
|
|
162
|
+
# This integer returns the maximum level of data structure nesting in
|
|
163
|
+
# the generated JSON, max_nesting = 0 if no maximum is checked.
|
|
164
|
+
attr_accessor :max_nesting
|
|
165
|
+
|
|
166
|
+
# This integer returns the current depth data structure nesting in the
|
|
167
|
+
# generated JSON.
|
|
168
|
+
attr_accessor :depth
|
|
169
|
+
|
|
170
|
+
def check_max_nesting # :nodoc:
|
|
171
|
+
return if @max_nesting.zero?
|
|
172
|
+
current_nesting = depth + 1
|
|
173
|
+
current_nesting > @max_nesting and
|
|
174
|
+
raise NestingError, "nesting of #{current_nesting} is too deep"
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
# Returns true, if circular data structures are checked,
|
|
121
178
|
# otherwise returns false.
|
|
122
179
|
def check_circular?
|
|
123
|
-
|
|
180
|
+
!@max_nesting.zero?
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
# Returns true if NaN, Infinity, and -Infinity should be considered as
|
|
184
|
+
# valid JSON and output.
|
|
185
|
+
def allow_nan?
|
|
186
|
+
@allow_nan
|
|
124
187
|
end
|
|
125
188
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
def seen?(object)
|
|
129
|
-
@seen.key?(object.__id__)
|
|
189
|
+
def ascii_only?
|
|
190
|
+
@ascii_only
|
|
130
191
|
end
|
|
131
192
|
|
|
132
|
-
#
|
|
133
|
-
#
|
|
134
|
-
def
|
|
135
|
-
@
|
|
193
|
+
# Configure this State instance with the Hash _opts_, and return
|
|
194
|
+
# itself.
|
|
195
|
+
def configure(opts)
|
|
196
|
+
@indent = opts[:indent] if opts.key?(:indent)
|
|
197
|
+
@space = opts[:space] if opts.key?(:space)
|
|
198
|
+
@space_before = opts[:space_before] if opts.key?(:space_before)
|
|
199
|
+
@object_nl = opts[:object_nl] if opts.key?(:object_nl)
|
|
200
|
+
@array_nl = opts[:array_nl] if opts.key?(:array_nl)
|
|
201
|
+
@allow_nan = !!opts[:allow_nan] if opts.key?(:allow_nan)
|
|
202
|
+
@ascii_only = opts[:ascii_only] if opts.key?(:ascii_only)
|
|
203
|
+
@depth = opts[:depth] || 0
|
|
204
|
+
if !opts.key?(:max_nesting) # defaults to 19
|
|
205
|
+
@max_nesting = 19
|
|
206
|
+
elsif opts[:max_nesting]
|
|
207
|
+
@max_nesting = opts[:max_nesting]
|
|
208
|
+
else
|
|
209
|
+
@max_nesting = 0
|
|
210
|
+
end
|
|
211
|
+
self
|
|
136
212
|
end
|
|
137
213
|
|
|
138
|
-
#
|
|
139
|
-
|
|
140
|
-
|
|
214
|
+
# Returns the configuration instance variables as a hash, that can be
|
|
215
|
+
# passed to the configure method.
|
|
216
|
+
def to_h
|
|
217
|
+
result = {}
|
|
218
|
+
for iv in %w[indent space space_before object_nl array_nl allow_nan max_nesting ascii_only depth]
|
|
219
|
+
result[iv.intern] = instance_variable_get("@#{iv}")
|
|
220
|
+
end
|
|
221
|
+
result
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
# Generates a valid JSON document from object +obj+ and returns the
|
|
225
|
+
# result. If no valid JSON document can be created this method raises a
|
|
226
|
+
# GeneratorError exception.
|
|
227
|
+
def generate(obj)
|
|
228
|
+
result = obj.to_json(self)
|
|
229
|
+
if result !~ /\A\s*(?:\[.*\]|\{.*\})\s*\Z/m
|
|
230
|
+
raise GeneratorError, "only generation of JSON objects or arrays allowed"
|
|
231
|
+
end
|
|
232
|
+
result
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
# Return the value returned by method +name+.
|
|
236
|
+
def [](name)
|
|
237
|
+
__send__ name
|
|
141
238
|
end
|
|
142
239
|
end
|
|
143
240
|
|
|
@@ -155,48 +252,40 @@ module JSON
|
|
|
155
252
|
# _state_ is a JSON::State object, that can also be used to configure the
|
|
156
253
|
# produced JSON string output further.
|
|
157
254
|
# _depth_ is used to find out nesting depth, to indent accordingly.
|
|
158
|
-
def to_json(state = nil,
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
else
|
|
163
|
-
json_transform(state, depth)
|
|
164
|
-
end
|
|
255
|
+
def to_json(state = nil, *)
|
|
256
|
+
state = State.from_state(state)
|
|
257
|
+
state.check_max_nesting
|
|
258
|
+
json_transform(state)
|
|
165
259
|
end
|
|
166
260
|
|
|
167
261
|
private
|
|
168
262
|
|
|
169
|
-
def
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
"circular data structures not supported!"
|
|
173
|
-
state.remember self
|
|
174
|
-
end
|
|
175
|
-
yield
|
|
176
|
-
ensure
|
|
177
|
-
state and state.forget self
|
|
178
|
-
end
|
|
179
|
-
|
|
180
|
-
def json_shift(state, depth)
|
|
181
|
-
state and not state.object_nl.empty? or return ''
|
|
182
|
-
state.indent * depth
|
|
263
|
+
def json_shift(state)
|
|
264
|
+
state.object_nl.empty? or return ''
|
|
265
|
+
state.indent * state.depth
|
|
183
266
|
end
|
|
184
267
|
|
|
185
|
-
def json_transform(state
|
|
268
|
+
def json_transform(state)
|
|
186
269
|
delim = ','
|
|
187
|
-
delim << state.object_nl
|
|
270
|
+
delim << state.object_nl
|
|
188
271
|
result = '{'
|
|
189
|
-
result << state.object_nl
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
272
|
+
result << state.object_nl
|
|
273
|
+
depth = state.depth += 1
|
|
274
|
+
first = true
|
|
275
|
+
indent = !state.object_nl.empty?
|
|
276
|
+
each { |key,value|
|
|
277
|
+
result << delim unless first
|
|
278
|
+
result << state.indent * depth if indent
|
|
279
|
+
result << key.to_s.to_json(state)
|
|
280
|
+
result << state.space_before
|
|
281
|
+
result << ':'
|
|
282
|
+
result << state.space
|
|
283
|
+
result << value.to_json(state)
|
|
284
|
+
first = false
|
|
285
|
+
}
|
|
286
|
+
depth = state.depth -= 1
|
|
287
|
+
result << state.object_nl
|
|
288
|
+
result << state.indent * depth if indent if indent
|
|
200
289
|
result << '}'
|
|
201
290
|
result
|
|
202
291
|
end
|
|
@@ -207,46 +296,32 @@ module JSON
|
|
|
207
296
|
# this Array instance.
|
|
208
297
|
# _state_ is a JSON::State object, that can also be used to configure the
|
|
209
298
|
# produced JSON string output further.
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
json_check_circular(state) { json_transform(state, depth) }
|
|
215
|
-
else
|
|
216
|
-
json_transform(state, depth)
|
|
217
|
-
end
|
|
299
|
+
def to_json(state = nil, *)
|
|
300
|
+
state = State.from_state(state)
|
|
301
|
+
state.check_max_nesting
|
|
302
|
+
json_transform(state)
|
|
218
303
|
end
|
|
219
304
|
|
|
220
305
|
private
|
|
221
306
|
|
|
222
|
-
def
|
|
223
|
-
if state
|
|
224
|
-
state.seen?(self) and raise JSON::CircularDatastructure,
|
|
225
|
-
"circular data structures not supported!"
|
|
226
|
-
state.remember self
|
|
227
|
-
end
|
|
228
|
-
yield
|
|
229
|
-
ensure
|
|
230
|
-
state and state.forget self
|
|
231
|
-
end
|
|
232
|
-
|
|
233
|
-
def json_shift(state, depth)
|
|
234
|
-
state and not state.array_nl.empty? or return ''
|
|
235
|
-
state.indent * depth
|
|
236
|
-
end
|
|
237
|
-
|
|
238
|
-
def json_transform(state, depth)
|
|
307
|
+
def json_transform(state)
|
|
239
308
|
delim = ','
|
|
240
|
-
delim << state.array_nl
|
|
309
|
+
delim << state.array_nl
|
|
241
310
|
result = '['
|
|
242
|
-
result << state.array_nl
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
311
|
+
result << state.array_nl
|
|
312
|
+
depth = state.depth += 1
|
|
313
|
+
first = true
|
|
314
|
+
indent = !state.array_nl.empty?
|
|
315
|
+
each { |value|
|
|
316
|
+
result << delim unless first
|
|
317
|
+
result << state.indent * depth if indent
|
|
318
|
+
result << value.to_json(state)
|
|
319
|
+
first = false
|
|
320
|
+
}
|
|
321
|
+
depth = state.depth -= 1
|
|
322
|
+
result << state.array_nl
|
|
323
|
+
result << state.indent * depth if indent
|
|
248
324
|
result << ']'
|
|
249
|
-
result
|
|
250
325
|
end
|
|
251
326
|
end
|
|
252
327
|
|
|
@@ -257,22 +332,65 @@ module JSON
|
|
|
257
332
|
|
|
258
333
|
module Float
|
|
259
334
|
# Returns a JSON string representation for this Float number.
|
|
260
|
-
def to_json(*)
|
|
335
|
+
def to_json(state = nil, *)
|
|
336
|
+
state = State.from_state(state)
|
|
337
|
+
case
|
|
338
|
+
when infinite?
|
|
339
|
+
if state.allow_nan?
|
|
340
|
+
to_s
|
|
341
|
+
else
|
|
342
|
+
raise GeneratorError, "#{self} not allowed in JSON"
|
|
343
|
+
end
|
|
344
|
+
when nan?
|
|
345
|
+
if state.allow_nan?
|
|
346
|
+
to_s
|
|
347
|
+
else
|
|
348
|
+
raise GeneratorError, "#{self} not allowed in JSON"
|
|
349
|
+
end
|
|
350
|
+
else
|
|
351
|
+
to_s
|
|
352
|
+
end
|
|
353
|
+
end
|
|
261
354
|
end
|
|
262
355
|
|
|
263
356
|
module String
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
357
|
+
if defined?(::Encoding)
|
|
358
|
+
# This string should be encoded with UTF-8 A call to this method
|
|
359
|
+
# returns a JSON string encoded with UTF16 big endian characters as
|
|
360
|
+
# \u????.
|
|
361
|
+
def to_json(state = nil, *args)
|
|
362
|
+
state = State.from_state(state)
|
|
363
|
+
if encoding == ::Encoding::UTF_8
|
|
364
|
+
string = self
|
|
365
|
+
else
|
|
366
|
+
string = encode(::Encoding::UTF_8)
|
|
367
|
+
end
|
|
368
|
+
if state.ascii_only?
|
|
369
|
+
'"' << JSON.utf8_to_json_ascii(string) << '"'
|
|
370
|
+
else
|
|
371
|
+
'"' << JSON.utf8_to_json(string) << '"'
|
|
372
|
+
end
|
|
373
|
+
end
|
|
374
|
+
else
|
|
375
|
+
# This string should be encoded with UTF-8 A call to this method
|
|
376
|
+
# returns a JSON string encoded with UTF16 big endian characters as
|
|
377
|
+
# \u????.
|
|
378
|
+
def to_json(state = nil, *args)
|
|
379
|
+
state = State.from_state(state)
|
|
380
|
+
if state.ascii_only?
|
|
381
|
+
'"' << JSON.utf8_to_json_ascii(self) << '"'
|
|
382
|
+
else
|
|
383
|
+
'"' << JSON.utf8_to_json(self) << '"'
|
|
384
|
+
end
|
|
385
|
+
end
|
|
269
386
|
end
|
|
270
387
|
|
|
271
388
|
# Module that holds the extinding methods if, the String module is
|
|
272
389
|
# included.
|
|
273
390
|
module Extend
|
|
274
|
-
# Raw Strings are JSON Objects (the raw bytes are stored in an
|
|
275
|
-
# key "raw"). The Ruby String can be created by this
|
|
391
|
+
# Raw Strings are JSON Objects (the raw bytes are stored in an
|
|
392
|
+
# array for the key "raw"). The Ruby String can be created by this
|
|
393
|
+
# module method.
|
|
276
394
|
def json_create(o)
|
|
277
395
|
o['raw'].pack('C*')
|
|
278
396
|
end
|
data/lib/json/pure/parser.rb
CHANGED
|
@@ -6,9 +6,12 @@ module JSON
|
|
|
6
6
|
# into a Ruby data structure.
|
|
7
7
|
class Parser < StringScanner
|
|
8
8
|
STRING = /" ((?:[^\x0-\x1f"\\] |
|
|
9
|
+
# escaped special characters:
|
|
9
10
|
\\["\\\/bfnrt] |
|
|
10
|
-
\\u[0-9a-fA-F]{4}
|
|
11
|
-
|
|
11
|
+
\\u[0-9a-fA-F]{4} |
|
|
12
|
+
# match all but escaped special characters:
|
|
13
|
+
\\[\x20-\x21\x23-\x2e\x30-\x5b\x5d-\x61\x63-\x65\x67-\x6d\x6f-\x71\x73\x75-\xff])*)
|
|
14
|
+
"/nx
|
|
12
15
|
INTEGER = /(-?0|-?[1-9]\d*)/
|
|
13
16
|
FLOAT = /(-?
|
|
14
17
|
(?:0|[1-9]\d*)
|
|
@@ -18,6 +21,9 @@ module JSON
|
|
|
18
21
|
(?i:e[+-]?\d+)
|
|
19
22
|
)
|
|
20
23
|
)/x
|
|
24
|
+
NAN = /NaN/
|
|
25
|
+
INFINITY = /Infinity/
|
|
26
|
+
MINUS_INFINITY = /-Infinity/
|
|
21
27
|
OBJECT_OPEN = /\{/
|
|
22
28
|
OBJECT_CLOSE = /\}/
|
|
23
29
|
ARRAY_OPEN = /\[/
|
|
@@ -45,9 +51,75 @@ module JSON
|
|
|
45
51
|
UNPARSED = Object.new
|
|
46
52
|
|
|
47
53
|
# Creates a new JSON::Pure::Parser instance for the string _source_.
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
54
|
+
#
|
|
55
|
+
# It will be configured by the _opts_ hash. _opts_ can have the following
|
|
56
|
+
# keys:
|
|
57
|
+
# * *max_nesting*: The maximum depth of nesting allowed in the parsed data
|
|
58
|
+
# structures. Disable depth checking with :max_nesting => false|nil|0,
|
|
59
|
+
# it defaults to 19.
|
|
60
|
+
# * *allow_nan*: If set to true, allow NaN, Infinity and -Infinity in
|
|
61
|
+
# defiance of RFC 4627 to be parsed by the Parser. This option defaults
|
|
62
|
+
# to false.
|
|
63
|
+
# * *symbolize_names*: If set to true, returns symbols for the names
|
|
64
|
+
# (keys) in a JSON object. Otherwise strings are returned, which is also
|
|
65
|
+
# the default.
|
|
66
|
+
# * *create_additions*: If set to false, the Parser doesn't create
|
|
67
|
+
# additions even if a matchin class and create_id was found. This option
|
|
68
|
+
# defaults to true.
|
|
69
|
+
# * *object_class*: Defaults to Hash
|
|
70
|
+
# * *array_class*: Defaults to Array
|
|
71
|
+
def initialize(source, opts = {})
|
|
72
|
+
opts ||= {}
|
|
73
|
+
if defined?(::Encoding)
|
|
74
|
+
if source.encoding == ::Encoding::ASCII_8BIT
|
|
75
|
+
b = source[0, 4].bytes.to_a
|
|
76
|
+
source = case
|
|
77
|
+
when b.size >= 4 && b[0] == 0 && b[1] == 0 && b[2] == 0
|
|
78
|
+
source.dup.force_encoding(::Encoding::UTF_32BE).encode!(::Encoding::UTF_8)
|
|
79
|
+
when b.size >= 4 && b[0] == 0 && b[2] == 0
|
|
80
|
+
source.dup.force_encoding(::Encoding::UTF_16BE).encode!(::Encoding::UTF_8)
|
|
81
|
+
when b.size >= 4 && b[1] == 0 && b[2] == 0 && b[3] == 0
|
|
82
|
+
source.dup.force_encoding(::Encoding::UTF_32LE).encode!(::Encoding::UTF_8)
|
|
83
|
+
|
|
84
|
+
when b.size >= 4 && b[1] == 0 && b[3] == 0
|
|
85
|
+
source.dup.force_encoding(::Encoding::UTF_16LE).encode!(::Encoding::UTF_8)
|
|
86
|
+
else
|
|
87
|
+
source.dup
|
|
88
|
+
end
|
|
89
|
+
else
|
|
90
|
+
source = source.encode(::Encoding::UTF_8)
|
|
91
|
+
end
|
|
92
|
+
source.force_encoding(::Encoding::ASCII_8BIT)
|
|
93
|
+
else
|
|
94
|
+
b = source
|
|
95
|
+
source = case
|
|
96
|
+
when b.size >= 4 && b[0] == 0 && b[1] == 0 && b[2] == 0
|
|
97
|
+
JSON.iconv('utf-8', 'utf-32be', b)
|
|
98
|
+
when b.size >= 4 && b[0] == 0 && b[2] == 0
|
|
99
|
+
JSON.iconv('utf-8', 'utf-16be', b)
|
|
100
|
+
when b.size >= 4 && b[1] == 0 && b[2] == 0 && b[3] == 0
|
|
101
|
+
JSON.iconv('utf-8', 'utf-32le', b)
|
|
102
|
+
when b.size >= 4 && b[1] == 0 && b[3] == 0
|
|
103
|
+
JSON.iconv('utf-8', 'utf-16le', b)
|
|
104
|
+
else
|
|
105
|
+
b
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
super source
|
|
109
|
+
if !opts.key?(:max_nesting) # defaults to 19
|
|
110
|
+
@max_nesting = 19
|
|
111
|
+
elsif opts[:max_nesting]
|
|
112
|
+
@max_nesting = opts[:max_nesting]
|
|
113
|
+
else
|
|
114
|
+
@max_nesting = 0
|
|
115
|
+
end
|
|
116
|
+
@allow_nan = !!opts[:allow_nan]
|
|
117
|
+
@symbolize_names = !!opts[:symbolize_names]
|
|
118
|
+
ca = true
|
|
119
|
+
ca = opts[:create_additions] if opts.key?(:create_additions)
|
|
120
|
+
@create_id = ca ? JSON.create_id : nil
|
|
121
|
+
@object_class = opts[:object_class] || Hash
|
|
122
|
+
@array_class = opts[:array_class] || Array
|
|
51
123
|
end
|
|
52
124
|
|
|
53
125
|
alias source string
|
|
@@ -61,9 +133,11 @@ module JSON
|
|
|
61
133
|
case
|
|
62
134
|
when scan(OBJECT_OPEN)
|
|
63
135
|
obj and raise ParserError, "source '#{peek(20)}' not in JSON!"
|
|
136
|
+
@current_nesting = 1
|
|
64
137
|
obj = parse_object
|
|
65
138
|
when scan(ARRAY_OPEN)
|
|
66
139
|
obj and raise ParserError, "source '#{peek(20)}' not in JSON!"
|
|
140
|
+
@current_nesting = 1
|
|
67
141
|
obj = parse_array
|
|
68
142
|
when skip(IGNORE)
|
|
69
143
|
;
|
|
@@ -77,30 +151,40 @@ module JSON
|
|
|
77
151
|
|
|
78
152
|
private
|
|
79
153
|
|
|
154
|
+
# Unescape characters in strings.
|
|
155
|
+
UNESCAPE_MAP = Hash.new { |h, k| h[k] = k.chr }
|
|
156
|
+
UNESCAPE_MAP.update({
|
|
157
|
+
?" => '"',
|
|
158
|
+
?\\ => '\\',
|
|
159
|
+
?/ => '/',
|
|
160
|
+
?b => "\b",
|
|
161
|
+
?f => "\f",
|
|
162
|
+
?n => "\n",
|
|
163
|
+
?r => "\r",
|
|
164
|
+
?t => "\t",
|
|
165
|
+
?u => nil,
|
|
166
|
+
})
|
|
167
|
+
|
|
80
168
|
def parse_string
|
|
81
169
|
if scan(STRING)
|
|
82
170
|
return '' if self[1].empty?
|
|
83
|
-
self[1].gsub(%r((?:\\[\\bfnrt"/]|(?:\\u(?:[A-Fa-f\d]{4}))
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
when '\\/' then '/'
|
|
88
|
-
when '\\b' then "\b"
|
|
89
|
-
when '\\f' then "\f"
|
|
90
|
-
when '\\n' then "\n"
|
|
91
|
-
when '\\r' then "\r"
|
|
92
|
-
when '\\t' then "\t"
|
|
93
|
-
else
|
|
171
|
+
string = self[1].gsub(%r((?:\\[\\bfnrt"/]|(?:\\u(?:[A-Fa-f\d]{4}))+|\\[\x20-\xff]))n) do |c|
|
|
172
|
+
if u = UNESCAPE_MAP[$&[1]]
|
|
173
|
+
u
|
|
174
|
+
else # \uXXXX
|
|
94
175
|
bytes = ''
|
|
95
|
-
c = $~[0]
|
|
96
176
|
i = 0
|
|
97
|
-
while c[6 * i] == ?\\ && c[6 * i + 1] == ?u
|
|
177
|
+
while c[6 * i] == ?\\ && c[6 * i + 1] == ?u
|
|
98
178
|
bytes << c[6 * i + 2, 2].to_i(16) << c[6 * i + 4, 2].to_i(16)
|
|
99
179
|
i += 1
|
|
100
180
|
end
|
|
101
181
|
JSON::UTF16toUTF8.iconv(bytes)
|
|
102
182
|
end
|
|
103
183
|
end
|
|
184
|
+
if string.respond_to?(:force_encoding)
|
|
185
|
+
string.force_encoding(::Encoding::UTF_8)
|
|
186
|
+
end
|
|
187
|
+
string
|
|
104
188
|
else
|
|
105
189
|
UNPARSED
|
|
106
190
|
end
|
|
@@ -123,16 +207,30 @@ module JSON
|
|
|
123
207
|
when (string = parse_string) != UNPARSED
|
|
124
208
|
string
|
|
125
209
|
when scan(ARRAY_OPEN)
|
|
126
|
-
|
|
210
|
+
@current_nesting += 1
|
|
211
|
+
ary = parse_array
|
|
212
|
+
@current_nesting -= 1
|
|
213
|
+
ary
|
|
127
214
|
when scan(OBJECT_OPEN)
|
|
128
|
-
|
|
215
|
+
@current_nesting += 1
|
|
216
|
+
obj = parse_object
|
|
217
|
+
@current_nesting -= 1
|
|
218
|
+
obj
|
|
219
|
+
when @allow_nan && scan(NAN)
|
|
220
|
+
NaN
|
|
221
|
+
when @allow_nan && scan(INFINITY)
|
|
222
|
+
Infinity
|
|
223
|
+
when @allow_nan && scan(MINUS_INFINITY)
|
|
224
|
+
MinusInfinity
|
|
129
225
|
else
|
|
130
226
|
UNPARSED
|
|
131
227
|
end
|
|
132
228
|
end
|
|
133
229
|
|
|
134
230
|
def parse_array
|
|
135
|
-
|
|
231
|
+
raise NestingError, "nesting of #@current_nesting is too deep" if
|
|
232
|
+
@max_nesting.nonzero? && @current_nesting > @max_nesting
|
|
233
|
+
result = @array_class.new
|
|
136
234
|
delim = false
|
|
137
235
|
until eos?
|
|
138
236
|
case
|
|
@@ -162,7 +260,9 @@ module JSON
|
|
|
162
260
|
end
|
|
163
261
|
|
|
164
262
|
def parse_object
|
|
165
|
-
|
|
263
|
+
raise NestingError, "nesting of #@current_nesting is too deep" if
|
|
264
|
+
@max_nesting.nonzero? && @current_nesting > @max_nesting
|
|
265
|
+
result = @object_class.new
|
|
166
266
|
delim = false
|
|
167
267
|
until eos?
|
|
168
268
|
case
|
|
@@ -173,7 +273,7 @@ module JSON
|
|
|
173
273
|
end
|
|
174
274
|
skip(IGNORE)
|
|
175
275
|
unless (value = parse_value).equal? UNPARSED
|
|
176
|
-
result[string] = value
|
|
276
|
+
result[@symbolize_names ? string.to_sym : string] = value
|
|
177
277
|
delim = false
|
|
178
278
|
skip(IGNORE)
|
|
179
279
|
if scan(COLLECTION_DELIMITER)
|
|
@@ -190,11 +290,10 @@ module JSON
|
|
|
190
290
|
if delim
|
|
191
291
|
raise ParserError, "expected next name, value pair in object at '#{peek(20)}'!"
|
|
192
292
|
end
|
|
193
|
-
if klassname = result[@create_id]
|
|
293
|
+
if @create_id and klassname = result[@create_id]
|
|
194
294
|
klass = JSON.deep_const_get klassname
|
|
195
295
|
break unless klass and klass.json_creatable?
|
|
196
296
|
result = klass.json_create(result)
|
|
197
|
-
result
|
|
198
297
|
end
|
|
199
298
|
break
|
|
200
299
|
when skip(IGNORE)
|