39
# Convert a UTF8 encoded Ruby string _string_ to a JSON string, encoded with
40
# UTF16 big endian characters as \u????, and return it.
41
if defined?(::Encoding)
42
def utf8_to_json(string) # :nodoc:
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)
51
def utf8_to_json_ascii(string) # :nodoc:
53
string << '' # XXX workaround: avoid buffer sharing
54
string.force_encoding(::Encoding::ASCII_8BIT)
55
string.gsub!(/["\\\x0-\x1f]/) { MAP[$&] }
58
[\xc2-\xdf][\x80-\xbf] |
59
[\xe0-\xef][\x80-\xbf]{2} |
60
[\xf0-\xf4][\x80-\xbf]{3}
62
[\x80-\xc1\xf5-\xff] # invalid
64
c.size == 1 and raise GeneratorError, "invalid utf8 byte: '#{c}'"
65
s = JSON.iconv('utf-16be', 'utf-8', c).unpack('H*')[0]
66
s.gsub!(/.{4}/n, '\\\\u\&')
68
string.force_encoding(::Encoding::UTF_8)
71
raise GeneratorError, "Caught #{e.class}: #{e}"
74
def utf8_to_json(string) # :nodoc:
75
string.gsub(/["\\\x0-\x1f]/) { MAP[$&] }
78
def utf8_to_json_ascii(string) # :nodoc:
79
string = string.gsub(/["\\\x0-\x1f]/) { MAP[$&] }
82
[\xc2-\xdf][\x80-\xbf] |
83
[\xe0-\xef][\x80-\xbf]{2} |
84
[\xf0-\xf4][\x80-\xbf]{3}
86
[\x80-\xc1\xf5-\xff] # invalid
88
c.size == 1 and raise GeneratorError, "invalid utf8 byte: '#{c}'"
89
s = JSON.iconv('utf-16be', 'utf-8', c).unpack('H*')[0]
90
s.gsub!(/.{4}/n, '\\\\u\&')
94
raise GeneratorError, "Caught #{e.class}: #{e}"
97
module_function :utf8_to_json, :utf8_to_json_ascii
101
# This class is used to create State instances, that are use to hold data
102
# while generating a JSON text from a a Ruby data structure.
104
# Creates a State object from _opts_, which ought to be Hash to create
105
# a new State instance configured by _opts_, something else to create
106
# an unconfigured instance. If _opts_ is a State object, it is just
108
def self.from_state(opts)
112
when opts.respond_to?(:to_hash)
114
when opts.respond_to?(:to_h)
117
SAFE_STATE_PROTOTYPE.dup
121
# Instantiates a new State object, configured by _opts_.
123
# _opts_ can have the following keys:
125
# * *indent*: a string used to indent levels (default: ''),
126
# * *space*: a string that is put after, a : or , delimiter (default: ''),
127
# * *space_before*: a string that is put before a : pair delimiter (default: ''),
128
# * *object_nl*: a string that is put at the end of a JSON object (default: ''),
129
# * *array_nl*: a string that is put at the end of a JSON array (default: ''),
130
# * *check_circular*: is deprecated now, use the :max_nesting option instead,
131
# * *max_nesting*: sets the maximum level of data structure nesting in
132
# the generated JSON, max_nesting = 0 if no maximum should be checked.
133
# * *allow_nan*: true if NaN, Infinity, and -Infinity should be
134
# generated, otherwise an exception is thrown, if these values are
135
# encountered. This options defaults to false.
136
def initialize(opts = {})
147
# This string is used to indent levels in the JSON text.
148
attr_accessor :indent
150
# This string is used to insert a space between the tokens in a JSON
154
# This string is used to insert a space before the ':' in JSON objects.
155
attr_accessor :space_before
157
# This string is put at the end of a line that holds a JSON object (or
159
attr_accessor :object_nl
161
# This string is put at the end of a line that holds a JSON array.
162
attr_accessor :array_nl
164
# This integer returns the maximum level of data structure nesting in
165
# the generated JSON, max_nesting = 0 if no maximum is checked.
166
attr_accessor :max_nesting
168
# This integer returns the current depth data structure nesting in the
172
def check_max_nesting # :nodoc:
173
return if @max_nesting.zero?
174
current_nesting = depth + 1
175
current_nesting > @max_nesting and
176
raise NestingError, "nesting of #{current_nesting} is too deep"
179
# Returns true, if circular data structures are checked,
180
# otherwise returns false.
185
# Returns true if NaN, Infinity, and -Infinity should be considered as
186
# valid JSON and output.
195
# Configure this State instance with the Hash _opts_, and return
198
@indent = opts[:indent] if opts.key?(:indent)
199
@space = opts[:space] if opts.key?(:space)
200
@space_before = opts[:space_before] if opts.key?(:space_before)
201
@object_nl = opts[:object_nl] if opts.key?(:object_nl)
202
@array_nl = opts[:array_nl] if opts.key?(:array_nl)
203
@allow_nan = !!opts[:allow_nan] if opts.key?(:allow_nan)
204
@ascii_only = opts[:ascii_only] if opts.key?(:ascii_only)
205
@depth = opts[:depth] || 0
206
if !opts.key?(:max_nesting) # defaults to 19
208
elsif opts[:max_nesting]
209
@max_nesting = opts[:max_nesting]
216
# Returns the configuration instance variables as a hash, that can be
217
# passed to the configure method.
220
for iv in %w[indent space space_before object_nl array_nl allow_nan max_nesting ascii_only depth]
221
result[iv.intern] = instance_variable_get("@#{iv}")
226
# Generates a valid JSON document from object +obj+ and returns the
227
# result. If no valid JSON document can be created this method raises a
228
# GeneratorError exception.
230
result = obj.to_json(self)
231
if result !~ /\A\s*(?:\[.*\]|\{.*\})\s*\Z/m
232
raise GeneratorError, "only generation of JSON objects or arrays allowed"
237
# Return the value returned by method +name+.
243
module GeneratorMethods
245
# Converts this object to a string (calling #to_s), converts
246
# it to a JSON string, and returns the result. This is a fallback, if no
247
# special method #to_json was defined for some object.
248
def to_json(*) to_s.to_json end
252
# Returns a JSON string containing a JSON object, that is unparsed from
253
# this Hash instance.
254
# _state_ is a JSON::State object, that can also be used to configure the
255
# produced JSON string output further.
256
# _depth_ is used to find out nesting depth, to indent accordingly.
257
def to_json(state = nil, *)
258
state = State.from_state(state)
259
state.check_max_nesting
260
json_transform(state)
265
def json_shift(state)
266
state.object_nl.empty? or return ''
267
state.indent * state.depth
270
def json_transform(state)
272
delim << state.object_nl
274
result << state.object_nl
275
depth = state.depth += 1
277
indent = !state.object_nl.empty?
279
result << delim unless first
280
result << state.indent * depth if indent
281
result << key.to_s.to_json(state)
282
result << state.space_before
284
result << state.space
285
result << value.to_json(state)
288
depth = state.depth -= 1
289
result << state.object_nl
290
result << state.indent * depth if indent if indent
297
# Returns a JSON string containing a JSON array, that is unparsed from
298
# this Array instance.
299
# _state_ is a JSON::State object, that can also be used to configure the
300
# produced JSON string output further.
301
def to_json(state = nil, *)
302
state = State.from_state(state)
303
state.check_max_nesting
304
json_transform(state)
309
def json_transform(state)
311
delim << state.array_nl
313
result << state.array_nl
314
depth = state.depth += 1
316
indent = !state.array_nl.empty?
318
result << delim unless first
319
result << state.indent * depth if indent
320
result << value.to_json(state)
323
depth = state.depth -= 1
324
result << state.array_nl
325
result << state.indent * depth if indent
331
# Returns a JSON string representation for this Integer number.
332
def to_json(*) to_s end
336
# Returns a JSON string representation for this Float number.
337
def to_json(state = nil, *)
338
state = State.from_state(state)
344
raise GeneratorError, "#{self} not allowed in JSON"
350
raise GeneratorError, "#{self} not allowed in JSON"
359
if defined?(::Encoding)
360
# This string should be encoded with UTF-8 A call to this method
361
# returns a JSON string encoded with UTF16 big endian characters as
363
def to_json(state = nil, *args)
364
state = State.from_state(state)
365
if encoding == ::Encoding::UTF_8
368
string = encode(::Encoding::UTF_8)
371
'"' << JSON.utf8_to_json_ascii(string) << '"'
373
'"' << JSON.utf8_to_json(string) << '"'
377
# This string should be encoded with UTF-8 A call to this method
378
# returns a JSON string encoded with UTF16 big endian characters as
380
def to_json(state = nil, *args)
381
state = State.from_state(state)
383
'"' << JSON.utf8_to_json_ascii(self) << '"'
385
'"' << JSON.utf8_to_json(self) << '"'
390
# Module that holds the extinding methods if, the String module is
393
# Raw Strings are JSON Objects (the raw bytes are stored in an
394
# array for the key "raw"). The Ruby String can be created by this
401
# Extends _modul_ with the String::Extend module.
402
def self.included(modul)
406
# This method creates a raw object hash, that can be nested into
407
# other data structures and will be unparsed as a raw string. This
408
# method should be used, if you want to convert raw strings to JSON
409
# instead of UTF-8 strings, e. g. binary data.
410
def to_json_raw_object
412
JSON.create_id => self.class.name,
413
'raw' => self.unpack('C*'),
417
# This method creates a JSON text from the result of
418
# a call to to_json_raw_object of this String.
419
def to_json_raw(*args)
420
to_json_raw_object.to_json(*args)
425
# Returns a JSON string for true: 'true'.
426
def to_json(*) 'true' end
430
# Returns a JSON string for false: 'false'.
431
def to_json(*) 'false' end
435
# Returns a JSON string for nil: 'null'.
436
def to_json(*) 'null' end