| 1 | # = json - JSON library for Ruby |
|---|
| 2 | # |
|---|
| 3 | # == Description |
|---|
| 4 | # |
|---|
| 5 | # == Author |
|---|
| 6 | # |
|---|
| 7 | # Florian Frank <mailto:flori@ping.de> |
|---|
| 8 | # |
|---|
| 9 | # == License |
|---|
| 10 | # |
|---|
| 11 | # This is free software; you can redistribute it and/or modify it under the |
|---|
| 12 | # terms of the GNU General Public License Version 2 as published by the Free |
|---|
| 13 | # Software Foundation: www.gnu.org/copyleft/gpl.html |
|---|
| 14 | # |
|---|
| 15 | # == Download |
|---|
| 16 | # |
|---|
| 17 | # The latest version of this library can be downloaded at |
|---|
| 18 | # |
|---|
| 19 | # * http://rubyforge.org/frs?group_id=953 |
|---|
| 20 | # |
|---|
| 21 | # Online Documentation should be located at |
|---|
| 22 | # |
|---|
| 23 | # * http://json.rubyforge.org |
|---|
| 24 | # |
|---|
| 25 | # == Examples |
|---|
| 26 | # |
|---|
| 27 | # To create a JSON string from a ruby data structure, you |
|---|
| 28 | # can call JSON.unparse (or JSON.generate) like that: |
|---|
| 29 | # |
|---|
| 30 | # json = JSON.unparse [1, 2, {"a"=>3.141}, false, true, nil, 4..10] |
|---|
| 31 | # # => "[1,2,{\"a\":3.141},false,true,null,\"4..10\"]" |
|---|
| 32 | # |
|---|
| 33 | # It's also possible to call the #to_json method directly. |
|---|
| 34 | # |
|---|
| 35 | # json = [1, 2, {"a"=>3.141}, false, true, nil, 4..10].to_json |
|---|
| 36 | # # => "[1,2,{\"a\":3.141},false,true,null,\"4..10\"]" |
|---|
| 37 | # |
|---|
| 38 | # To get back a ruby data structure, you have to call |
|---|
| 39 | # JSON.parse on the JSON string: |
|---|
| 40 | # |
|---|
| 41 | # JSON.parse json |
|---|
| 42 | # # => [1, 2, {"a"=>3.141}, false, true, nil, "4..10"] |
|---|
| 43 | # |
|---|
| 44 | # Note, that the range from the original data structure is a simple |
|---|
| 45 | # string now. The reason for this is, that JSON doesn't support ranges |
|---|
| 46 | # or arbitrary classes. In this case the json library falls back to call |
|---|
| 47 | # Object#to_json, which is the same as #to_s.to_json. |
|---|
| 48 | # |
|---|
| 49 | # It's possible to extend JSON to support serialization of arbitray classes by |
|---|
| 50 | # simply implementing a more specialized version of the #to_json method, that |
|---|
| 51 | # should return a JSON object (a hash converted to JSON with #to_json) |
|---|
| 52 | # like this (don't forget the *a for all the arguments): |
|---|
| 53 | # |
|---|
| 54 | # class Range |
|---|
| 55 | # def to_json(*a) |
|---|
| 56 | # { |
|---|
| 57 | # 'json_class' => self.class.name, |
|---|
| 58 | # 'data' => [ first, last, exclude_end? ] |
|---|
| 59 | # }.to_json(*a) |
|---|
| 60 | # end |
|---|
| 61 | # end |
|---|
| 62 | # |
|---|
| 63 | # The hash key 'json_class' is the class, that will be asked to deserialize the |
|---|
| 64 | # JSON representation later. In this case it's 'Range', but any namespace of |
|---|
| 65 | # the form 'A::B' or '::A::B' will do. All other keys are arbitrary and can be |
|---|
| 66 | # used to store the necessary data to configure the object to be deserialized. |
|---|
| 67 | # |
|---|
| 68 | # If a the key 'json_class' is found in a JSON object, the JSON parser checks |
|---|
| 69 | # if the given class responds to the json_create class method. If so, it is |
|---|
| 70 | # called with the JSON object converted to a Ruby hash. So a range can |
|---|
| 71 | # be deserialized by implementing Range.json_create like this: |
|---|
| 72 | # |
|---|
| 73 | # class Range |
|---|
| 74 | # def self.json_create(o) |
|---|
| 75 | # new(*o['data']) |
|---|
| 76 | # end |
|---|
| 77 | # end |
|---|
| 78 | # |
|---|
| 79 | # Now it possible to serialize/deserialize ranges as well: |
|---|
| 80 | # |
|---|
| 81 | # json = JSON.unparse [1, 2, {"a"=>3.141}, false, true, nil, 4..10] |
|---|
| 82 | # # => "[1,2,{\"a\":3.141},false,true,null,{\"json_class\":\"Range\",\"data\":[4,10,false]}]" |
|---|
| 83 | # JSON.parse json |
|---|
| 84 | # # => [1, 2, {"a"=>3.141}, false, true, nil, 4..10] |
|---|
| 85 | # |
|---|
| 86 | # JSON.unparse always creates the shortes possible string representation of a |
|---|
| 87 | # ruby data structure in one line. This good for data storage or network |
|---|
| 88 | # protocols, but not so good for humans to read. Fortunately there's |
|---|
| 89 | # also JSON.pretty_unparse (or JSON.pretty_generate) that creates a more |
|---|
| 90 | # readable output: |
|---|
| 91 | # |
|---|
| 92 | # puts JSON.pretty_unparse([1, 2, {"a"=>3.141}, false, true, nil, 4..10]) |
|---|
| 93 | # [ |
|---|
| 94 | # 1, |
|---|
| 95 | # 2, |
|---|
| 96 | # { |
|---|
| 97 | # "a": 3.141 |
|---|
| 98 | # }, |
|---|
| 99 | # false, |
|---|
| 100 | # true, |
|---|
| 101 | # null, |
|---|
| 102 | # { |
|---|
| 103 | # "json_class": "Range", |
|---|
| 104 | # "data": [ |
|---|
| 105 | # 4, |
|---|
| 106 | # 10, |
|---|
| 107 | # false |
|---|
| 108 | # ] |
|---|
| 109 | # } |
|---|
| 110 | # ] |
|---|
| 111 | # |
|---|
| 112 | # There are also the methods Kernel#j for unparse, and Kernel#jj for |
|---|
| 113 | # pretty_unparse output to the console, that work analogous to Kernel#p and |
|---|
| 114 | # Kernel#pp. |
|---|
| 115 | # |
|---|
| 116 | |
|---|
| 117 | require 'strscan' |
|---|
| 118 | |
|---|
| 119 | # This module is the namespace for all the JSON related classes. It also |
|---|
| 120 | # defines some module functions to expose a nicer API to users, instead |
|---|
| 121 | # of using the parser and other classes directly. |
|---|
| 122 | module JSON |
|---|
| 123 | # The base exception for JSON errors. |
|---|
| 124 | JSONError = Class.new StandardError |
|---|
| 125 | |
|---|
| 126 | # This exception is raise, if a parser error occurs. |
|---|
| 127 | ParserError = Class.new JSONError |
|---|
| 128 | |
|---|
| 129 | # This exception is raise, if a unparser error occurs. |
|---|
| 130 | UnparserError = Class.new JSONError |
|---|
| 131 | |
|---|
| 132 | # If a circular data structure is encountered while unparsing |
|---|
| 133 | # this exception is raised. |
|---|
| 134 | CircularDatastructure = Class.new UnparserError |
|---|
| 135 | |
|---|
| 136 | class << self |
|---|
| 137 | # Switches on Unicode support, if _enable_ is _true_. Otherwise switches |
|---|
| 138 | # Unicode support off. |
|---|
| 139 | def support_unicode=(enable) |
|---|
| 140 | @support_unicode = enable |
|---|
| 141 | end |
|---|
| 142 | |
|---|
| 143 | # Returns _true_ if JSON supports unicode, otherwise _false_ is returned. |
|---|
| 144 | # |
|---|
| 145 | # If loading of the iconv library fails, or it doesn't support utf8/utf16 |
|---|
| 146 | # encoding, this will be set to false, as a fallback. |
|---|
| 147 | def support_unicode? |
|---|
| 148 | !!@support_unicode |
|---|
| 149 | end |
|---|
| 150 | end |
|---|
| 151 | JSON.support_unicode = true # default, however it's possible to switch off |
|---|
| 152 | # full unicode support, if non-ascii bytes should be |
|---|
| 153 | # just passed through. |
|---|
| 154 | |
|---|
| 155 | begin |
|---|
| 156 | require 'iconv' |
|---|
| 157 | # An iconv instance to convert from UTF8 to UTF16 Big Endian. |
|---|
| 158 | UTF16toUTF8 = Iconv.new('utf-8', 'utf-16be') |
|---|
| 159 | # An iconv instance to convert from UTF16 Big Endian to UTF8. |
|---|
| 160 | UTF8toUTF16 = Iconv.new('utf-16be', 'utf-8'); UTF8toUTF16.iconv('no bom') |
|---|
| 161 | rescue Errno::EINVAL, Iconv::InvalidEncoding |
|---|
| 162 | # Iconv doesn't support big endian utf-16. Let's try to hack this manually |
|---|
| 163 | # into the converters. |
|---|
| 164 | begin |
|---|
| 165 | old_verbose = $VERBOSE |
|---|
| 166 | $VERBOSE = nil |
|---|
| 167 | # An iconv instance to convert from UTF8 to UTF16 Big Endian. |
|---|
| 168 | UTF16toUTF8 = Iconv.new('utf-8', 'utf-16') |
|---|
| 169 | # An iconv instance to convert from UTF16 Big Endian to UTF8. |
|---|
| 170 | UTF8toUTF16 = Iconv.new('utf-16', 'utf-8'); UTF8toUTF16.iconv('no bom') |
|---|
| 171 | if UTF8toUTF16.iconv("\xe2\x82\xac") == "\xac\x20" |
|---|
| 172 | swapper = Class.new do |
|---|
| 173 | def initialize(iconv) |
|---|
| 174 | @iconv = iconv |
|---|
| 175 | end |
|---|
| 176 | |
|---|
| 177 | def iconv(string) |
|---|
| 178 | result = @iconv.iconv(string) |
|---|
| 179 | JSON.swap!(result) |
|---|
| 180 | end |
|---|
| 181 | end |
|---|
| 182 | UTF8toUTF16 = swapper.new(UTF8toUTF16) |
|---|
| 183 | end |
|---|
| 184 | if UTF16toUTF8.iconv("\xac\x20") == "\xe2\x82\xac" |
|---|
| 185 | swapper = Class.new do |
|---|
| 186 | def initialize(iconv) |
|---|
| 187 | @iconv = iconv |
|---|
| 188 | end |
|---|
| 189 | |
|---|
| 190 | def iconv(string) |
|---|
| 191 | string = JSON.swap!(string.dup) |
|---|
| 192 | @iconv.iconv(string) |
|---|
| 193 | end |
|---|
| 194 | end |
|---|
| 195 | UTF16toUTF8 = swapper.new(UTF16toUTF8) |
|---|
| 196 | end |
|---|
| 197 | rescue Errno::EINVAL, Iconv::InvalidEncoding |
|---|
| 198 | # Enforce disabling of unicode support, if iconv doesn't support |
|---|
| 199 | # UTF8/UTF16 at all. |
|---|
| 200 | JSON.support_unicode = false |
|---|
| 201 | ensure |
|---|
| 202 | $VERBOSE = old_verbose |
|---|
| 203 | end |
|---|
| 204 | rescue LoadError |
|---|
| 205 | # Enforce disabling of unicode support, if iconv doesn't exist. |
|---|
| 206 | JSON.support_unicode = false |
|---|
| 207 | end |
|---|
| 208 | |
|---|
| 209 | # Swap consecutive bytes in string in place. |
|---|
| 210 | def self.swap!(string) |
|---|
| 211 | 0.upto(string.size / 2) do |i| |
|---|
| 212 | break unless string[2 * i + 1] |
|---|
| 213 | string[2 * i], string[2 * i + 1] = string[2 * i + 1], string[2 * i] |
|---|
| 214 | end |
|---|
| 215 | string |
|---|
| 216 | end |
|---|
| 217 | |
|---|
| 218 | # This class implements the JSON parser that is used to parse a JSON string |
|---|
| 219 | # into a Ruby data structure. |
|---|
| 220 | class Parser < StringScanner |
|---|
| 221 | STRING = /"((?:[^"\\]|\\.)*)"/ |
|---|
| 222 | INTEGER = /-?(?:0|[1-9]\d*)/ |
|---|
| 223 | FLOAT = /-?(?:0|[1-9]\d*)\.(\d+)(?i:e[+-]?\d+)?/ |
|---|
| 224 | OBJECT_OPEN = /\{/ |
|---|
| 225 | OBJECT_CLOSE = /\}/ |
|---|
| 226 | ARRAY_OPEN = /\[/ |
|---|
| 227 | ARRAY_CLOSE = /\]/ |
|---|
| 228 | PAIR_DELIMITER = /:/ |
|---|
| 229 | COLLECTION_DELIMITER = /,/ |
|---|
| 230 | TRUE = /true/ |
|---|
| 231 | FALSE = /false/ |
|---|
| 232 | NULL = /null/ |
|---|
| 233 | IGNORE = %r( |
|---|
| 234 | (?: |
|---|
| 235 | //[^\n\r]*[\n\r]| # line comments |
|---|
| 236 | /\* # c-style comments |
|---|
| 237 | (?: |
|---|
| 238 | [^*/]| # normal chars |
|---|
| 239 | /[^*]| # slashes that do not start a nested comment |
|---|
| 240 | \*[^/]| # asterisks that do not end this comment |
|---|
| 241 | /(?=\*/) # single slash before this comment's end |
|---|
| 242 | )* |
|---|
| 243 | \*/ # the end of this comment |
|---|
| 244 | |[ \t\r\n]+ # whitespaces: space, horicontal tab, lf, cr |
|---|
| 245 | )+ |
|---|
| 246 | )mx |
|---|
| 247 | |
|---|
| 248 | UNPARSED = Object.new |
|---|
| 249 | |
|---|
| 250 | # Parses the current JSON string and returns the complete data structure |
|---|
| 251 | # as a result. |
|---|
| 252 | def parse |
|---|
| 253 | reset |
|---|
| 254 | obj = nil |
|---|
| 255 | until eos? |
|---|
| 256 | case |
|---|
| 257 | when scan(OBJECT_OPEN) |
|---|
| 258 | obj and raise ParserError, "source '#{peek(20)}' not in JSON!" |
|---|
| 259 | obj = parse_object |
|---|
| 260 | when scan(ARRAY_OPEN) |
|---|
| 261 | obj and raise ParserError, "source '#{peek(20)}' not in JSON!" |
|---|
| 262 | obj = parse_array |
|---|
| 263 | when skip(IGNORE) |
|---|
| 264 | ; |
|---|
| 265 | else |
|---|
| 266 | raise ParserError, "source '#{peek(20)}' not in JSON!" |
|---|
| 267 | end |
|---|
| 268 | end |
|---|
| 269 | obj or raise ParserError, "source did not contain any JSON!" |
|---|
| 270 | obj |
|---|
| 271 | end |
|---|
| 272 | |
|---|
| 273 | private |
|---|
| 274 | |
|---|
| 275 | def parse_string |
|---|
| 276 | if scan(STRING) |
|---|
| 277 | return '' if self[1].empty? |
|---|
| 278 | self[1].gsub(%r(\\(?:[\\bfnrt"/]|u([A-Fa-f\d]{4})))) do |
|---|
| 279 | case $~[0] |
|---|
| 280 | when '\\"' then '"' |
|---|
| 281 | when '\\\\' then '\\' |
|---|
| 282 | when '\\/' then '/' |
|---|
| 283 | when '\\b' then "\b" |
|---|
| 284 | when '\\f' then "\f" |
|---|
| 285 | when '\\n' then "\n" |
|---|
| 286 | when '\\r' then "\r" |
|---|
| 287 | when '\\t' then "\t" |
|---|
| 288 | else |
|---|
| 289 | if JSON.support_unicode? and $KCODE == 'UTF8' |
|---|
| 290 | JSON.utf16_to_utf8($~[1]) |
|---|
| 291 | else |
|---|
| 292 | # if utf8 mode is switched off or unicode not supported, try to |
|---|
| 293 | # transform unicode \u-notation to bytes directly: |
|---|
| 294 | $~[1].to_i(16).chr |
|---|
| 295 | end |
|---|
| 296 | end |
|---|
| 297 | end |
|---|
| 298 | else |
|---|
| 299 | UNPARSED |
|---|
| 300 | end |
|---|
| 301 | end |
|---|
| 302 | |
|---|
| 303 | def parse_value |
|---|
| 304 | case |
|---|
| 305 | when scan(FLOAT) |
|---|
| 306 | Float(self[0].sub(/\.([eE])/, '.0\1')) |
|---|
| 307 | when scan(INTEGER) |
|---|
| 308 | Integer(self[0]) |
|---|
| 309 | when scan(TRUE) |
|---|
| 310 | true |
|---|
| 311 | when scan(FALSE) |
|---|
| 312 | false |
|---|
| 313 | when scan(NULL) |
|---|
| 314 | nil |
|---|
| 315 | when (string = parse_string) != UNPARSED |
|---|
| 316 | string |
|---|
| 317 | when scan(ARRAY_OPEN) |
|---|
| 318 | parse_array |
|---|
| 319 | when scan(OBJECT_OPEN) |
|---|
| 320 | parse_object |
|---|
| 321 | else |
|---|
| 322 | UNPARSED |
|---|
| 323 | end |
|---|
| 324 | end |
|---|
| 325 | |
|---|
| 326 | def parse_array |
|---|
| 327 | result = [] |
|---|
| 328 | until eos? |
|---|
| 329 | case |
|---|
| 330 | when (value = parse_value) != UNPARSED |
|---|
| 331 | result << value |
|---|
| 332 | skip(IGNORE) |
|---|
| 333 | unless scan(COLLECTION_DELIMITER) or match?(ARRAY_CLOSE) |
|---|
| 334 | raise ParserError, "expected ',' or ']' in array at '#{peek(20)}'!" |
|---|
| 335 | end |
|---|
| 336 | when scan(ARRAY_CLOSE) |
|---|
| 337 | break |
|---|
| 338 | when skip(IGNORE) |
|---|
| 339 | ; |
|---|
| 340 | else |
|---|
| 341 | raise ParserError, "unexpected token in array at '#{peek(20)}'!" |
|---|
| 342 | end |
|---|
| 343 | end |
|---|
| 344 | result |
|---|
| 345 | end |
|---|
| 346 | |
|---|
| 347 | def parse_object |
|---|
| 348 | result = {} |
|---|
| 349 | until eos? |
|---|
| 350 | case |
|---|
| 351 | when (string = parse_string) != UNPARSED |
|---|
| 352 | skip(IGNORE) |
|---|
| 353 | unless scan(PAIR_DELIMITER) |
|---|
| 354 | raise ParserError, "expected ':' in object at '#{peek(20)}'!" |
|---|
| 355 | end |
|---|
| 356 | skip(IGNORE) |
|---|
| 357 | unless (value = parse_value).equal? UNPARSED |
|---|
| 358 | result[string] = value |
|---|
| 359 | skip(IGNORE) |
|---|
| 360 | unless scan(COLLECTION_DELIMITER) or match?(OBJECT_CLOSE) |
|---|
| 361 | raise ParserError, |
|---|
| 362 | "expected ',' or '}' in object at '#{peek(20)}'!" |
|---|
| 363 | end |
|---|
| 364 | else |
|---|
| 365 | raise ParserError, "expected value in object at '#{peek(20)}'!" |
|---|
| 366 | end |
|---|
| 367 | when scan(OBJECT_CLOSE) |
|---|
| 368 | if klassname = result['json_class'] |
|---|
| 369 | klass = klassname.sub(/^:+/, '').split(/::/).inject(Object) do |p,k| |
|---|
| 370 | p.const_get(k) rescue nil |
|---|
| 371 | end |
|---|
| 372 | break unless klass and klass.json_creatable? |
|---|
| 373 | result = klass.json_create(result) |
|---|
| 374 | end |
|---|
| 375 | break |
|---|
| 376 | when skip(IGNORE) |
|---|
| 377 | ; |
|---|
| 378 | else |
|---|
| 379 | raise ParserError, "unexpected token in object at '#{peek(20)}'!" |
|---|
| 380 | end |
|---|
| 381 | end |
|---|
| 382 | result |
|---|
| 383 | end |
|---|
| 384 | end |
|---|
| 385 | |
|---|
| 386 | # This class is used to create State instances, that are use to hold data |
|---|
| 387 | # while unparsing a Ruby data structure into a JSON string. |
|---|
| 388 | class State |
|---|
| 389 | # Creates a State object from _opts_, which ought to be Hash to create a |
|---|
| 390 | # new State instance configured by opts, something else to create an |
|---|
| 391 | # unconfigured instance. If _opts_ is a State object, it is just returned. |
|---|
| 392 | def self.from_state(opts) |
|---|
| 393 | case opts |
|---|
| 394 | when self |
|---|
| 395 | opts |
|---|
| 396 | when Hash |
|---|
| 397 | new(opts) |
|---|
| 398 | else |
|---|
| 399 | new |
|---|
| 400 | end |
|---|
| 401 | end |
|---|
| 402 | |
|---|
| 403 | # Instantiates a new State object, configured by _opts_. |
|---|
| 404 | def initialize(opts = {}) |
|---|
| 405 | @indent = opts[:indent] || '' |
|---|
| 406 | @space = opts[:space] || '' |
|---|
| 407 | @object_nl = opts[:object_nl] || '' |
|---|
| 408 | @array_nl = opts[:array_nl] || '' |
|---|
| 409 | @seen = {} |
|---|
| 410 | end |
|---|
| 411 | |
|---|
| 412 | # This string is used to indent levels in the JSON string. |
|---|
| 413 | attr_accessor :indent |
|---|
| 414 | |
|---|
| 415 | # This string is used to include a space between the tokens in a JSON |
|---|
| 416 | # string. |
|---|
| 417 | attr_accessor :space |
|---|
| 418 | |
|---|
| 419 | # This string is put at the end of a line that holds a JSON object (or |
|---|
| 420 | # Hash). |
|---|
| 421 | attr_accessor :object_nl |
|---|
| 422 | |
|---|
| 423 | # This string is put at the end of a line that holds a JSON array. |
|---|
| 424 | attr_accessor :array_nl |
|---|
| 425 | |
|---|
| 426 | # Returns _true_, if _object_ was already seen during this Unparsing run. |
|---|
| 427 | def seen?(object) |
|---|
| 428 | @seen.key?(object.__id__) |
|---|
| 429 | end |
|---|
| 430 | |
|---|
| 431 | # Remember _object_, to find out if it was already encountered (to find out |
|---|
| 432 | # if a cyclic data structure is unparsed). |
|---|
| 433 | def remember(object) |
|---|
| 434 | @seen[object.__id__] = true |
|---|
| 435 | end |
|---|
| 436 | |
|---|
| 437 | # Forget _object_ for this Unparsing run. |
|---|
| 438 | def forget(object) |
|---|
| 439 | @seen.delete object.__id__ |
|---|
| 440 | end |
|---|
| 441 | end |
|---|
| 442 | |
|---|
| 443 | module_function |
|---|
| 444 | |
|---|
| 445 | # Convert _string_ from UTF8 encoding to UTF16 (big endian) encoding and |
|---|
| 446 | # return it. |
|---|
| 447 | def utf8_to_utf16(string) |
|---|
| 448 | JSON::UTF8toUTF16.iconv(string).unpack('H*')[0] |
|---|
| 449 | end |
|---|
| 450 | |
|---|
| 451 | # Convert _string_ from UTF16 (big endian) encoding to UTF8 encoding and |
|---|
| 452 | # return it. |
|---|
| 453 | def utf16_to_utf8(string) |
|---|
| 454 | bytes = '' << string[0, 2].to_i(16) << string[2, 2].to_i(16) |
|---|
| 455 | JSON::UTF16toUTF8.iconv(bytes) |
|---|
| 456 | end |
|---|
| 457 | |
|---|
| 458 | # Convert a UTF8 encoded Ruby string _string_ to a JSON string, encoded with |
|---|
| 459 | # UTF16 big endian characters as \u????, and return it. |
|---|
| 460 | def utf8_to_json(string) |
|---|
| 461 | i, n, result = 0, string.size, '' |
|---|
| 462 | while i < n |
|---|
| 463 | char = string[i] |
|---|
| 464 | case |
|---|
| 465 | when char == ?\b then result << '\b' |
|---|
| 466 | when char == ?\t then result << '\t' |
|---|
| 467 | when char == ?\n then result << '\n' |
|---|
| 468 | when char == ?\f then result << '\f' |
|---|
| 469 | when char == ?\r then result << '\r' |
|---|
| 470 | when char == ?" then result << '\"' |
|---|
| 471 | when char == ?\\ then result << '\\\\' |
|---|
| 472 | when char == ?/ then result << '\/' |
|---|
| 473 | when char.between?(0x0, 0x1f) then result << "\\u%04x" % char |
|---|
| 474 | when char.between?(0x20, 0x7f) then result << char |
|---|
| 475 | when !(JSON.support_unicode? && $KCODE == 'UTF8') |
|---|
| 476 | # if utf8 mode is switched off or unicode not supported, just pass |
|---|
| 477 | # bytes through: |
|---|
| 478 | result << char |
|---|
| 479 | when char & 0xe0 == 0xc0 |
|---|
| 480 | result << '\u' << utf8_to_utf16(string[i, 2]) |
|---|
| 481 | i += 1 |
|---|
| 482 | when char & 0xf0 == 0xe0 |
|---|
| 483 | result << '\u' << utf8_to_utf16(string[i, 3]) |
|---|
| 484 | i += 2 |
|---|
| 485 | when char & 0xf8 == 0xf0 |
|---|
| 486 | result << '\u' << utf8_to_utf16(string[i, 4]) |
|---|
| 487 | i += 3 |
|---|
| 488 | when char & 0xfc == 0xf8 |
|---|
| 489 | result << '\u' << utf8_to_utf16(string[i, 5]) |
|---|
| 490 | i += 4 |
|---|
| 491 | when char & 0xfe == 0xfc |
|---|
| 492 | result << '\u' << utf8_to_utf16(string[i, 6]) |
|---|
| 493 | i += 5 |
|---|
| 494 | else |
|---|
| 495 | raise JSON::UnparserError, "Encountered unknown UTF-8 byte: %x!" % char |
|---|
| 496 | end |
|---|
| 497 | i += 1 |
|---|
| 498 | end |
|---|
| 499 | result |
|---|
| 500 | end |
|---|
| 501 | |
|---|
| 502 | # Parse the JSON string _source_ into a Ruby data structure and return it. |
|---|
| 503 | def parse(source) |
|---|
| 504 | Parser.new(source).parse |
|---|
| 505 | end |
|---|
| 506 | |
|---|
| 507 | # Unparse the Ruby data structure _obj_ into a single line JSON string and |
|---|
| 508 | # return it. _state_ is a JSON::State object, that can be used to configure |
|---|
| 509 | # the output further. |
|---|
| 510 | def unparse(obj, state = nil) |
|---|
| 511 | obj.to_json(JSON::State.from_state(state)) |
|---|
| 512 | end |
|---|
| 513 | |
|---|
| 514 | alias generate unparse |
|---|
| 515 | |
|---|
| 516 | # Unparse the Ruby data structure _obj_ into a JSON string and return it. |
|---|
| 517 | # The returned string is a prettier form of the string returned by #unparse. |
|---|
| 518 | def pretty_unparse(obj) |
|---|
| 519 | state = JSON::State.new( |
|---|
| 520 | :indent => ' ', |
|---|
| 521 | :space => ' ', |
|---|
| 522 | :object_nl => "\n", |
|---|
| 523 | :array_nl => "\n" |
|---|
| 524 | ) |
|---|
| 525 | obj.to_json(state) |
|---|
| 526 | end |
|---|
| 527 | |
|---|
| 528 | alias pretty_generate pretty_unparse |
|---|
| 529 | end |
|---|
| 530 | |
|---|
| 531 | class Object |
|---|
| 532 | # Converts this object to a string (calling #to_s), converts |
|---|
| 533 | # it to a JSON string, and returns the result. This is a fallback, if no |
|---|
| 534 | # special method #to_json was defined for some object. |
|---|
| 535 | # _state_ is a JSON::State object, that can also be used |
|---|
| 536 | # to configure the produced JSON string output further. |
|---|
| 537 | |
|---|
| 538 | def to_json(*) to_s.to_json end |
|---|
| 539 | end |
|---|
| 540 | |
|---|
| 541 | class Hash |
|---|
| 542 | # Returns a JSON string containing a JSON object, that is unparsed from |
|---|
| 543 | # this Hash instance. |
|---|
| 544 | # _state_ is a JSON::State object, that can also be used to configure the |
|---|
| 545 | # produced JSON string output further. |
|---|
| 546 | # _depth_ is used to find out nesting depth, to indent accordingly. |
|---|
| 547 | def to_json(state = nil, depth = 0) |
|---|
| 548 | state = JSON::State.from_state(state) |
|---|
| 549 | json_check_circular(state) { json_transform(state, depth) } |
|---|
| 550 | end |
|---|
| 551 | |
|---|
| 552 | private |
|---|
| 553 | |
|---|
| 554 | def json_check_circular(state) |
|---|
| 555 | if state |
|---|
| 556 | state.seen?(self) and raise JSON::CircularDatastructure, |
|---|
| 557 | "circular data structures not supported!" |
|---|
| 558 | state.remember self |
|---|
| 559 | end |
|---|
| 560 | yield |
|---|
| 561 | ensure |
|---|
| 562 | state and state.forget self |
|---|
| 563 | end |
|---|
| 564 | |
|---|
| 565 | def json_shift(state, depth) |
|---|
| 566 | state and not state.object_nl.empty? or return '' |
|---|
| 567 | state.indent * depth |
|---|
| 568 | end |
|---|
| 569 | |
|---|
| 570 | def json_transform(state, depth) |
|---|
| 571 | delim = ',' |
|---|
| 572 | delim << state.object_nl if state |
|---|
| 573 | result = '{' |
|---|
| 574 | result << state.object_nl if state |
|---|
| 575 | result << map { |key,value| |
|---|
| 576 | json_shift(state, depth + 1) << |
|---|
| 577 | key.to_s.to_json(state, depth + 1) << |
|---|
| 578 | ':' << state.space << value.to_json(state, depth + 1) |
|---|
| 579 | }.join(delim) |
|---|
| 580 | result << state.object_nl if state |
|---|
| 581 | result << json_shift(state, depth) |
|---|
| 582 | result << '}' |
|---|
| 583 | result |
|---|
| 584 | end |
|---|
| 585 | end |
|---|
| 586 | |
|---|
| 587 | class Array |
|---|
| 588 | # Returns a JSON string containing a JSON array, that is unparsed from |
|---|
| 589 | # this Array instance. |
|---|
| 590 | # _state_ is a JSON::State object, that can also be used to configure the |
|---|
| 591 | # produced JSON string output further. |
|---|
| 592 | # _depth_ is used to find out nesting depth, to indent accordingly. |
|---|
| 593 | def to_json(state = nil, depth = 0) |
|---|
| 594 | state = JSON::State.from_state(state) |
|---|
| 595 | json_check_circular(state) { json_transform(state, depth) } |
|---|
| 596 | end |
|---|
| 597 | |
|---|
| 598 | private |
|---|
| 599 | |
|---|
| 600 | def json_check_circular(state) |
|---|
| 601 | if state |
|---|
| 602 | state.seen?(self) and raise JSON::CircularDatastructure, |
|---|
| 603 | "circular data structures not supported!" |
|---|
| 604 | state.remember self |
|---|
| 605 | end |
|---|
| 606 | yield |
|---|
| 607 | ensure |
|---|
| 608 | state and state.forget self |
|---|
| 609 | end |
|---|
| 610 | |
|---|
| 611 | def json_shift(state, depth) |
|---|
| 612 | state and not state.array_nl.empty? or return '' |
|---|
| 613 | state.indent * depth |
|---|
| 614 | end |
|---|
| 615 | |
|---|
| 616 | def json_transform(state, depth) |
|---|
| 617 | delim = ',' |
|---|
| 618 | delim << state.array_nl if state |
|---|
| 619 | result = '[' |
|---|
| 620 | result << state.array_nl if state |
|---|
| 621 | result << map { |value| |
|---|
| 622 | json_shift(state, depth + 1) << value.to_json(state, depth + 1) |
|---|
| 623 | }.join(delim) |
|---|
| 624 | result << state.array_nl if state |
|---|
| 625 | result << json_shift(state, depth) |
|---|
| 626 | result << ']' |
|---|
| 627 | result |
|---|
| 628 | end |
|---|
| 629 | end |
|---|
| 630 | |
|---|
| 631 | class Integer |
|---|
| 632 | # Returns a JSON string representation for this Integer number. |
|---|
| 633 | def to_json(*) to_s end |
|---|
| 634 | end |
|---|
| 635 | |
|---|
| 636 | class Float |
|---|
| 637 | # Returns a JSON string representation for this Float number. |
|---|
| 638 | def to_json(*) to_s end |
|---|
| 639 | end |
|---|
| 640 | |
|---|
| 641 | class String |
|---|
| 642 | # This string should be encoded with UTF-8 (if JSON unicode support is |
|---|
| 643 | # enabled). A call to this method returns a JSON string |
|---|
| 644 | # encoded with UTF16 big endian characters as \u????. If |
|---|
| 645 | # JSON.support_unicode? is false only control characters are encoded this |
|---|
| 646 | # way, all 8-bit bytes are just passed through. |
|---|
| 647 | def to_json(*) |
|---|
| 648 | '"' << JSON::utf8_to_json(self) << '"' |
|---|
| 649 | end |
|---|
| 650 | |
|---|
| 651 | # Raw Strings are JSON Objects (the raw bytes are stored in an array for the |
|---|
| 652 | # key "raw"). The Ruby String can be created by this class method. |
|---|
| 653 | def self.json_create(o) |
|---|
| 654 | o['raw'].pack('C*') |
|---|
| 655 | end |
|---|
| 656 | |
|---|
| 657 | # This method creates a raw object, that can be nested into other data |
|---|
| 658 | # structures and will be unparsed as a raw string. |
|---|
| 659 | def to_json_raw_object |
|---|
| 660 | { |
|---|
| 661 | 'json_class' => self.class.name, |
|---|
| 662 | 'raw' => self.unpack('C*'), |
|---|
| 663 | } |
|---|
| 664 | end |
|---|
| 665 | |
|---|
| 666 | # This method should be used, if you want to convert raw strings to JSON |
|---|
| 667 | # instead of UTF-8 strings, e. g. binary data (and JSON Unicode support is |
|---|
| 668 | # enabled). |
|---|
| 669 | def to_json_raw(*args) |
|---|
| 670 | to_json_raw_object.to_json(*args) |
|---|
| 671 | end |
|---|
| 672 | end |
|---|
| 673 | |
|---|
| 674 | class TrueClass |
|---|
| 675 | # Returns a JSON string for true: 'true'. |
|---|
| 676 | def to_json(*) to_s end |
|---|
| 677 | end |
|---|
| 678 | |
|---|
| 679 | class FalseClass |
|---|
| 680 | # Returns a JSON string for false: 'false'. |
|---|
| 681 | def to_json(*) to_s end |
|---|
| 682 | end |
|---|
| 683 | |
|---|
| 684 | class NilClass |
|---|
| 685 | # Returns a JSON string for nil: 'null'. |
|---|
| 686 | def to_json(*) 'null' end |
|---|
| 687 | end |
|---|
| 688 | |
|---|
| 689 | module Kernel |
|---|
| 690 | # Outputs _objs_ to STDOUT as JSON strings in the shortest form, that is in |
|---|
| 691 | # one line. |
|---|
| 692 | def j(*objs) |
|---|
| 693 | objs.each do |obj| |
|---|
| 694 | puts JSON::generate(obj) |
|---|
| 695 | end |
|---|
| 696 | nil |
|---|
| 697 | end |
|---|
| 698 | |
|---|
| 699 | # Ouputs _objs_ to STDOUT as JSON strings in a pretty format, with |
|---|
| 700 | # indentation and over many lines. |
|---|
| 701 | def jj(*objs) |
|---|
| 702 | objs.each do |obj| |
|---|
| 703 | puts JSON::pretty_generate(obj) |
|---|
| 704 | end |
|---|
| 705 | nil |
|---|
| 706 | end |
|---|
| 707 | end |
|---|
| 708 | |
|---|
| 709 | class Class |
|---|
| 710 | # Returns true, if this class can be used to create an instance |
|---|
| 711 | # from a serialised JSON string. The class has to implement a class |
|---|
| 712 | # method _json_create_ that expects a hash as first parameter, which includes |
|---|
| 713 | # the required data. |
|---|
| 714 | def json_creatable? |
|---|
| 715 | respond_to?(:json_create) |
|---|
| 716 | end |
|---|
| 717 | end |
|---|
| 718 | # vim: set et sw=2 ts=2: |
|---|