Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Merge pull request #10 from sstephenson/multi_json

---

This patch:

1. Removes the ActiveSupport::JSON engine so that Rails 3.1 can avoid a circular dependency when the Sprockets and ExecJS libraries require MultiJSON. (As the original author of ActiveSupport::JSON, Ill also be pushing for Rails to remove it and defer to MultiJSON instead.)
2. Bundles the [OkJson](https://github.com/kr/okjson) library and adds an Okjson engine that will be used if no other engines are available.
  • Loading branch information...
commit 4d24e4ca4ba6caa704f43615b56381e287932a27 2 parents fdad79f + e89093c
Josh Kalderimis joshk authored
12 README.rdoc
Source Rendered
@@ -3,20 +3,20 @@
3 3 Lots of Ruby libraries utilize JSON parsing in some form, and everyone has their favorite JSON library. In order to best support multiple JSON parsers and libraries, <tt>multi_json</tt> is a general-purpose swappable JSON backend library. You use it like so:
4 4
5 5 require 'multi_json'
6   -
  6 +
7 7 MultiJson.engine = :yajl
8 8 MultiJson.decode('{"abc":"def"}') # decoded using Yajl
9   -
  9 +
10 10 MultiJson.engine = :json_gem
11 11 MultiJson.engine = MultiJson::Engines::JsonGem # equivalent to previous line
12 12 MultiJson.encode({:abc => 'def'}) # encoded using the JSON gem
13   -
  13 +
14 14 The <tt>engine</tt> setter takes either a symbol or a class (to allow for custom JSON parsers) that responds to both <tt>.decode</tt> and <tt>.encode</tt> at the class level.
15 15
16   -MultiJSON tries to have intelligent defaulting. That is, if you have any of the supported engines already loaded, it will utilize them before attempting to load any. When loading, libraries are ordered by speed. First Yajl-Ruby, then the JSON gem, then ActiveSupport, then JSON pure.
  16 +MultiJSON tries to have intelligent defaulting. That is, if you have any of the supported engines already loaded, it will utilize them before attempting to load any. When loading, libraries are ordered by speed. First Yajl-Ruby, then the JSON gem, then JSON pure. If no JSON library is available, MultiJSON falls back to a bundled version of <a href="https://github.com/kr/okjson">OkJson</a>.
17 17
18 18 == Note on Patches/Pull Requests
19   -
  19 +
20 20 * Fork the project.
21 21 * Make your feature addition or bug fix.
22 22 * Add tests for it. This is important so I don't break it in a
@@ -27,4 +27,4 @@ MultiJSON tries to have intelligent defaulting. That is, if you have any of the
27 27
28 28 == Copyright
29 29
30   -Copyright (c) 2010 Michael Bleigh and Intridea, Inc. See LICENSE for details.
  30 +Copyright (c) 2010 Michael Bleigh and Intridea, Inc. See LICENSE for details.
7 lib/multi_json.rb
@@ -12,8 +12,8 @@ def engine
12 12 REQUIREMENT_MAP = [
13 13 ["yajl", :yajl],
14 14 ["json", :json_gem],
15   - ["active_support", :active_support],
16   - ["json/pure", :json_pure]
  15 + ["json/pure", :json_pure],
  16 + ["okjson", :okjson]
17 17 ]
18 18
19 19 # The default engine based on what you currently
@@ -23,7 +23,6 @@ def engine
23 23 def default_engine
24 24 return :yajl if defined?(::Yajl)
25 25 return :json_gem if defined?(::JSON)
26   - return :active_support if defined?(::ActiveSupport::JSON)
27 26
28 27 REQUIREMENT_MAP.each do |(library, engine)|
29 28 begin
@@ -40,7 +39,7 @@ def default_engine
40 39 #
41 40 # * <tt>:json_gem</tt>
42 41 # * <tt>:json_pure</tt>
43   - # * <tt>:active_support</tt> (useful for inside Rails apps)
  42 + # * <tt>:okjson</tt>
44 43 # * <tt>:yajl</tt>
45 44 def engine=(new_engine)
46 45 case new_engine
32 lib/multi_json/engines/active_support.rb
... ... @@ -1,32 +0,0 @@
1   -require 'active_support' unless defined?(::ActiveSupport::JSON)
2   -
3   -module MultiJson
4   - module Engines
5   - # Use ActiveSupport to encode/decode JSON.
6   - class ActiveSupport
7   - def self.decode(string, options = {}) #:nodoc:
8   - hash = ::ActiveSupport::JSON.decode(string)
9   - options[:symbolize_keys] ? symbolize_keys(hash) : hash
10   - end
11   -
12   - def self.encode(object) #:nodoc:
13   - ::ActiveSupport::JSON.encode(object)
14   - end
15   -
16   - def self.symbolize_keys(hash) #:nodoc:
17   - hash.inject({}){|result, (key, value)|
18   - new_key = case key
19   - when String then key.to_sym
20   - else key
21   - end
22   - new_value = case value
23   - when Hash then symbolize_keys(value)
24   - else value
25   - end
26   - result[new_key] = new_value
27   - result
28   - }
29   - end
30   - end
31   - end
32   -end
34 lib/multi_json/engines/okjson.rb
... ... @@ -0,0 +1,34 @@
  1 +require "multi_json/vendor/okjson" unless defined?(::OkJson)
  2 +
  3 +module MultiJson
  4 + module Engines
  5 + class Okjson
  6 + def self.decode(string, options = {}) #:nodoc:
  7 + result = OkJson.decode(string)
  8 + options[:symbolize_keys] ? symbolize_keys(result) : result
  9 + end
  10 +
  11 + def self.encode(object) #:nodoc:
  12 + OkJson.encode(stringify_keys(object))
  13 + end
  14 +
  15 + def self.symbolize_keys(object) #:nodoc:
  16 + return object unless object.is_a?(Hash)
  17 + object.inject({}) do |result, (key, value)|
  18 + new_key = key.is_a?(String) ? key.to_sym : key
  19 + new_value = value.is_a?(Hash) ? symbolize_keys(value) : value
  20 + result.merge! new_key => new_value
  21 + end
  22 + end
  23 +
  24 + def self.stringify_keys(object) #:nodoc:
  25 + return object unless object.is_a?(Hash)
  26 + object.inject({}) do |result, (key, value)|
  27 + new_key = key.is_a?(Symbol) ? key.to_s : key
  28 + new_value = value.is_a?(Hash) ? stringify_keys(value) : value
  29 + result.merge! new_key => new_value
  30 + end
  31 + end
  32 + end
  33 + end
  34 +end
581 lib/multi_json/vendor/okjson.rb
... ... @@ -0,0 +1,581 @@
  1 +# Copyright 2011 Keith Rarick
  2 +#
  3 +# Permission is hereby granted, free of charge, to any person obtaining a copy
  4 +# of this software and associated documentation files (the "Software"), to deal
  5 +# in the Software without restriction, including without limitation the rights
  6 +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  7 +# copies of the Software, and to permit persons to whom the Software is
  8 +# furnished to do so, subject to the following conditions:
  9 +#
  10 +# The above copyright notice and this permission notice shall be included in
  11 +# all copies or substantial portions of the Software.
  12 +#
  13 +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  14 +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  15 +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  16 +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  17 +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  18 +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  19 +# THE SOFTWARE.
  20 +
  21 +# See https://github.com/kr/okjson for updates.
  22 +
  23 +require 'stringio'
  24 +
  25 +# Some parts adapted from
  26 +# http://golang.org/src/pkg/json/decode.go and
  27 +# http://golang.org/src/pkg/utf8/utf8.go
  28 +module OkJson
  29 + extend self
  30 +
  31 +
  32 + # Decodes a json document in string s and
  33 + # returns the corresponding ruby value.
  34 + # String s must be valid UTF-8. If you have
  35 + # a string in some other encoding, convert
  36 + # it first.
  37 + #
  38 + # String values in the resulting structure
  39 + # will be UTF-8.
  40 + def decode(s)
  41 + ts = lex(s)
  42 + v, ts = textparse(ts)
  43 + if ts.length > 0
  44 + raise Error, 'trailing garbage'
  45 + end
  46 + v
  47 + end
  48 +
  49 +
  50 + # Parses a "json text" in the sense of RFC 4627.
  51 + # Returns the parsed value and any trailing tokens.
  52 + # Note: this is almost the same as valparse,
  53 + # except that it does not accept atomic values.
  54 + def textparse(ts)
  55 + if ts.length < 0
  56 + raise Error, 'empty'
  57 + end
  58 +
  59 + typ, _, val = ts[0]
  60 + case typ
  61 + when '{' then objparse(ts)
  62 + when '[' then arrparse(ts)
  63 + else
  64 + raise Error, "unexpected #{val.inspect}"
  65 + end
  66 + end
  67 +
  68 +
  69 + # Parses a "value" in the sense of RFC 4627.
  70 + # Returns the parsed value and any trailing tokens.
  71 + def valparse(ts)
  72 + if ts.length < 0
  73 + raise Error, 'empty'
  74 + end
  75 +
  76 + typ, _, val = ts[0]
  77 + case typ
  78 + when '{' then objparse(ts)
  79 + when '[' then arrparse(ts)
  80 + when :val,:str then [val, ts[1..-1]]
  81 + else
  82 + raise Error, "unexpected #{val.inspect}"
  83 + end
  84 + end
  85 +
  86 +
  87 + # Parses an "object" in the sense of RFC 4627.
  88 + # Returns the parsed value and any trailing tokens.
  89 + def objparse(ts)
  90 + ts = eat('{', ts)
  91 + obj = {}
  92 +
  93 + if ts[0][0] == '}'
  94 + return obj, ts[1..-1]
  95 + end
  96 +
  97 + k, v, ts = pairparse(ts)
  98 + obj[k] = v
  99 +
  100 + if ts[0][0] == '}'
  101 + return obj, ts[1..-1]
  102 + end
  103 +
  104 + loop do
  105 + ts = eat(',', ts)
  106 +
  107 + k, v, ts = pairparse(ts)
  108 + obj[k] = v
  109 +
  110 + if ts[0][0] == '}'
  111 + return obj, ts[1..-1]
  112 + end
  113 + end
  114 + end
  115 +
  116 +
  117 + # Parses a "member" in the sense of RFC 4627.
  118 + # Returns the parsed values and any trailing tokens.
  119 + def pairparse(ts)
  120 + (typ, _, k), ts = ts[0], ts[1..-1]
  121 + if typ != :str
  122 + raise Error, "unexpected #{k.inspect}"
  123 + end
  124 + ts = eat(':', ts)
  125 + v, ts = valparse(ts)
  126 + [k, v, ts]
  127 + end
  128 +
  129 +
  130 + # Parses an "array" in the sense of RFC 4627.
  131 + # Returns the parsed value and any trailing tokens.
  132 + def arrparse(ts)
  133 + ts = eat('[', ts)
  134 + arr = []
  135 +
  136 + if ts[0][0] == ']'
  137 + return arr, ts[1..-1]
  138 + end
  139 +
  140 + v, ts = valparse(ts)
  141 + arr << v
  142 +
  143 + if ts[0][0] == ']'
  144 + return arr, ts[1..-1]
  145 + end
  146 +
  147 + loop do
  148 + ts = eat(',', ts)
  149 +
  150 + v, ts = valparse(ts)
  151 + arr << v
  152 +
  153 + if ts[0][0] == ']'
  154 + return arr, ts[1..-1]
  155 + end
  156 + end
  157 + end
  158 +
  159 +
  160 + def eat(typ, ts)
  161 + if ts[0][0] != typ
  162 + raise Error, "expected #{typ} (got #{ts[0].inspect})"
  163 + end
  164 + ts[1..-1]
  165 + end
  166 +
  167 +
  168 + # Sans s and returns a list of json tokens,
  169 + # excluding white space (as defined in RFC 4627).
  170 + def lex(s)
  171 + ts = []
  172 + while s.length > 0
  173 + typ, lexeme, val = tok(s)
  174 + if typ == nil
  175 + raise Error, "invalid character at #{s[0,10].inspect}"
  176 + end
  177 + if typ != :space
  178 + ts << [typ, lexeme, val]
  179 + end
  180 + s = s[lexeme.length..-1]
  181 + end
  182 + ts
  183 + end
  184 +
  185 +
  186 + # Scans the first token in s and
  187 + # returns a 3-element list, or nil
  188 + # if no such token exists.
  189 + #
  190 + # The first list element is one of
  191 + # '{', '}', ':', ',', '[', ']',
  192 + # :val, :str, and :space.
  193 + #
  194 + # The second element is the lexeme.
  195 + #
  196 + # The third element is the value of the
  197 + # token for :val and :str, otherwise
  198 + # it is the lexeme.
  199 + def tok(s)
  200 + case s[0]
  201 + when ?{ then ['{', s[0,1], s[0,1]]
  202 + when ?} then ['}', s[0,1], s[0,1]]
  203 + when ?: then [':', s[0,1], s[0,1]]
  204 + when ?, then [',', s[0,1], s[0,1]]
  205 + when ?[ then ['[', s[0,1], s[0,1]]
  206 + when ?] then [']', s[0,1], s[0,1]]
  207 + when ?n then nulltok(s)
  208 + when ?t then truetok(s)
  209 + when ?f then falsetok(s)
  210 + when ?" then strtok(s)
  211 + when Spc then [:space, s[0,1], s[0,1]]
  212 + when ?\t then [:space, s[0,1], s[0,1]]
  213 + when ?\n then [:space, s[0,1], s[0,1]]
  214 + when ?\r then [:space, s[0,1], s[0,1]]
  215 + else numtok(s)
  216 + end
  217 + end
  218 +
  219 +
  220 + def nulltok(s); s[0,4] == 'null' && [:val, 'null', nil] end
  221 + def truetok(s); s[0,4] == 'true' && [:val, 'true', true] end
  222 + def falsetok(s); s[0,5] == 'false' && [:val, 'false', false] end
  223 +
  224 +
  225 + def numtok(s)
  226 + m = /-?([1-9][0-9]+|[0-9])([.][0-9]+)?([eE][+-]?[0-9]+)?/.match(s)
  227 + if m && m.begin(0) == 0
  228 + if m[3] && !m[2]
  229 + [:val, m[0], Integer(m[1])*(10**Integer(m[3][1..-1]))]
  230 + elsif m[2]
  231 + [:val, m[0], Float(m[0])]
  232 + else
  233 + [:val, m[0], Integer(m[0])]
  234 + end
  235 + end
  236 + end
  237 +
  238 +
  239 + def strtok(s)
  240 + m = /"([^"\\]|\\["\/\\bfnrt]|\\u[0-9a-fA-F]{4})*"/.match(s)
  241 + if ! m
  242 + raise Error, "invalid string literal at #{abbrev(s)}"
  243 + end
  244 + [:str, m[0], unquote(m[0])]
  245 + end
  246 +
  247 +
  248 + def abbrev(s)
  249 + t = s[0,10]
  250 + p = t['`']
  251 + t = t[0,p] if p
  252 + t = t + '...' if t.length < s.length
  253 + '`' + t + '`'
  254 + end
  255 +
  256 +
  257 + # Converts a quoted json string literal q into a UTF-8-encoded string.
  258 + # The rules are different than for Ruby, so we cannot use eval.
  259 + # Unquote will raise an error if q contains control characters.
  260 + def unquote(q)
  261 + q = q[1...-1]
  262 + a = q.dup # allocate a big enough string
  263 + r, w = 0, 0
  264 + while r < q.length
  265 + c = q[r]
  266 + case true
  267 + when c == ?\\
  268 + r += 1
  269 + if r >= q.length
  270 + raise Error, "string literal ends with a \"\\\": \"#{q}\""
  271 + end
  272 +
  273 + case q[r]
  274 + when ?",?\\,?/,?'
  275 + a[w] = q[r]
  276 + r += 1
  277 + w += 1
  278 + when ?b,?f,?n,?r,?t
  279 + a[w] = Unesc[q[r]]
  280 + r += 1
  281 + w += 1
  282 + when ?u
  283 + r += 1
  284 + uchar = begin
  285 + hexdec4(q[r,4])
  286 + rescue RuntimeError => e
  287 + raise Error, "invalid escape sequence \\u#{q[r,4]}: #{e}"
  288 + end
  289 + r += 4
  290 + if surrogate? uchar
  291 + if q.length >= r+6
  292 + uchar1 = hexdec4(q[r+2,4])
  293 + uchar = subst(uchar, uchar1)
  294 + if uchar != Ucharerr
  295 + # A valid pair; consume.
  296 + r += 6
  297 + end
  298 + end
  299 + end
  300 + w += ucharenc(a, w, uchar)
  301 + else
  302 + raise Error, "invalid escape char #{q[r]} in \"#{q}\""
  303 + end
  304 + when c == ?", c < Spc
  305 + raise Error, "invalid character in string literal \"#{q}\""
  306 + else
  307 + # Copy anything else byte-for-byte.
  308 + # Valid UTF-8 will remain valid UTF-8.
  309 + # Invalid UTF-8 will remain invalid UTF-8.
  310 + a[w] = c
  311 + r += 1
  312 + w += 1
  313 + end
  314 + end
  315 + a[0,w]
  316 + end
  317 +
  318 +
  319 + # Encodes unicode character u as UTF-8
  320 + # bytes in string a at position i.
  321 + # Returns the number of bytes written.
  322 + def ucharenc(a, i, u)
  323 + case true
  324 + when u <= Uchar1max
  325 + a[i] = (u & 0xff).chr
  326 + 1
  327 + when u <= Uchar2max
  328 + a[i+0] = (Utag2 | ((u>>6)&0xff)).chr
  329 + a[i+1] = (Utagx | (u&Umaskx)).chr
  330 + 2
  331 + when u <= Uchar3max
  332 + a[i+0] = (Utag3 | ((u>>12)&0xff)).chr
  333 + a[i+1] = (Utagx | ((u>>6)&Umaskx)).chr
  334 + a[i+2] = (Utagx | (u&Umaskx)).chr
  335 + 3
  336 + else
  337 + a[i+0] = (Utag4 | ((u>>18)&0xff)).chr
  338 + a[i+1] = (Utagx | ((u>>12)&Umaskx)).chr
  339 + a[i+2] = (Utagx | ((u>>6)&Umaskx)).chr
  340 + a[i+3] = (Utagx | (u&Umaskx)).chr
  341 + 4
  342 + end
  343 + end
  344 +
  345 +
  346 + def hexdec4(s)
  347 + if s.length != 4
  348 + raise Error, 'short'
  349 + end
  350 + (nibble(s[0])<<12) | (nibble(s[1])<<8) | (nibble(s[2])<<4) | nibble(s[3])
  351 + end
  352 +
  353 +
  354 + def subst(u1, u2)
  355 + if Usurr1 <= u1 && u1 < Usurr2 && Usurr2 <= u2 && u2 < Usurr3
  356 + return ((u1-Usurr1)<<10) | (u2-Usurr2) + Usurrself
  357 + end
  358 + return Ucharerr
  359 + end
  360 +
  361 +
  362 + def unsubst(u)
  363 + if u < Usurrself || u > Umax || surrogate?(u)
  364 + return Ucharerr, Ucharerr
  365 + end
  366 + u -= Usurrself
  367 + [Usurr1 + ((u>>10)&0x3ff), Usurr2 + (u&0x3ff)]
  368 + end
  369 +
  370 +
  371 + def surrogate?(u)
  372 + Usurr1 <= u && u < Usurr3
  373 + end
  374 +
  375 +
  376 + def nibble(c)
  377 + case true
  378 + when ?0 <= c && c <= ?9 then c.ord - ?0.ord
  379 + when ?a <= c && c <= ?z then c.ord - ?a.ord + 10
  380 + when ?A <= c && c <= ?Z then c.ord - ?A.ord + 10
  381 + else
  382 + raise Error, "invalid hex code #{c}"
  383 + end
  384 + end
  385 +
  386 +
  387 + # Encodes x into a json text. It may contain only
  388 + # Array, Hash, String, Numeric, true, false, nil.
  389 + # (Note, this list excludes Symbol.)
  390 + # X itself must be an Array or a Hash.
  391 + # No other value can be encoded, and an error will
  392 + # be raised if x contains any other value, such as
  393 + # Nan, Infinity, Symbol, and Proc, or if a Hash key
  394 + # is not a String.
  395 + # Strings contained in x must be valid UTF-8.
  396 + def encode(x)
  397 + case x
  398 + when Hash then objenc(x)
  399 + when Array then arrenc(x)
  400 + else
  401 + raise Error, 'root value must be an Array or a Hash'
  402 + end
  403 + end
  404 +
  405 +
  406 + def valenc(x)
  407 + case x
  408 + when Hash then objenc(x)
  409 + when Array then arrenc(x)
  410 + when String then strenc(x)
  411 + when Numeric then numenc(x)
  412 + when true then "true"
  413 + when false then "false"
  414 + when nil then "null"
  415 + else
  416 + raise Error, "cannot encode #{x.class}: #{x.inspect}"
  417 + end
  418 + end
  419 +
  420 +
  421 + def objenc(x)
  422 + '{' + x.map{|k,v| keyenc(k) + ':' + valenc(v)}.join(',') + '}'
  423 + end
  424 +
  425 +
  426 + def arrenc(a)
  427 + '[' + a.map{|x| valenc(x)}.join(',') + ']'
  428 + end
  429 +
  430 +
  431 + def keyenc(k)
  432 + case k
  433 + when String then strenc(k)
  434 + else
  435 + raise Error, "Hash key is not a string: #{k.inspect}"
  436 + end
  437 + end
  438 +
  439 +
  440 + def strenc(s)
  441 + t = StringIO.new
  442 + t.putc(?")
  443 + r = 0
  444 + while r < s.length
  445 + case s[r]
  446 + when ?" then t.print('\\"')
  447 + when ?\\ then t.print('\\\\')
  448 + when ?\b then t.print('\\b')
  449 + when ?\f then t.print('\\f')
  450 + when ?\n then t.print('\\n')
  451 + when ?\r then t.print('\\r')
  452 + when ?\t then t.print('\\t')
  453 + else
  454 + c = s[r]
  455 + case true
  456 + when Spc <= c && c <= ?~
  457 + t.putc(c)
  458 + when true
  459 + u, size = uchardec(s, r)
  460 + r += size - 1 # we add one more at the bottom of the loop
  461 + if u < 0x10000
  462 + t.print('\\u')
  463 + hexenc4(t, u)
  464 + else
  465 + u1, u2 = unsubst(u)
  466 + t.print('\\u')
  467 + hexenc4(t, u1)
  468 + t.print('\\u')
  469 + hexenc4(t, u2)
  470 + end
  471 + else
  472 + # invalid byte; skip it
  473 + end
  474 + end
  475 + r += 1
  476 + end
  477 + t.putc(?")
  478 + t.string
  479 + end
  480 +
  481 +
  482 + def hexenc4(t, u)
  483 + t.putc(Hex[(u>>12)&0xf])
  484 + t.putc(Hex[(u>>8)&0xf])
  485 + t.putc(Hex[(u>>4)&0xf])
  486 + t.putc(Hex[u&0xf])
  487 + end
  488 +
  489 +
  490 + def numenc(x)
  491 + if x.nan? || x.infinite?
  492 + return 'null'
  493 + end rescue nil
  494 + "#{x}"
  495 + end
  496 +
  497 +
  498 + # Decodes unicode character u from UTF-8
  499 + # bytes in string s at position i.
  500 + # Returns u and the number of bytes read.
  501 + def uchardec(s, i)
  502 + n = s.length - i
  503 + return [Ucharerr, 1] if n < 1
  504 +
  505 + c0 = s[i].ord
  506 +
  507 + # 1-byte, 7-bit sequence?
  508 + if c0 < Utagx
  509 + return [c0, 1]
  510 + end
  511 +
  512 + # unexpected continuation byte?
  513 + return [Ucharerr, 1] if c0 < Utag2
  514 +
  515 + # need continuation byte
  516 + return [Ucharerr, 1] if n < 2
  517 + c1 = s[i+1].ord
  518 + return [Ucharerr, 1] if c1 < Utagx || Utag2 <= c1
  519 +
  520 + # 2-byte, 11-bit sequence?
  521 + if c0 < Utag3
  522 + u = (c0&Umask2)<<6 | (c1&Umaskx)
  523 + return [Ucharerr, 1] if u <= Uchar1max
  524 + return [u, 2]
  525 + end
  526 +
  527 + # need second continuation byte
  528 + return [Ucharerr, 1] if n < 3
  529 + c2 = s[i+2].ord
  530 + return [Ucharerr, 1] if c2 < Utagx || Utag2 <= c2
  531 +
  532 + # 3-byte, 16-bit sequence?
  533 + if c0 < Utag4
  534 + u = (c0&Umask3)<<12 | (c1&Umaskx)<<6 | (c2&Umaskx)
  535 + return [Ucharerr, 1] if u <= Uchar2max
  536 + return [u, 3]
  537 + end
  538 +
  539 + # need third continuation byte
  540 + return [Ucharerr, 1] if n < 4
  541 + c3 = s[i+3].ord
  542 + return [Ucharerr, 1] if c3 < Utagx || Utag2 <= c3
  543 +
  544 + # 4-byte, 21-bit sequence?
  545 + if c0 < Utag5
  546 + u = (c0&Umask4)<<18 | (c1&Umaskx)<<12 | (c2&Umaskx)<<6 | (c3&Umaskx)
  547 + return [Ucharerr, 1] if u <= Uchar3max
  548 + return [u, 4]
  549 + end
  550 +
  551 + return [Ucharerr, 1]
  552 + end
  553 +
  554 +
  555 + class Error < ::StandardError
  556 + end
  557 +
  558 +
  559 + Utagx = 0x80 # 1000 0000
  560 + Utag2 = 0xc0 # 1100 0000
  561 + Utag3 = 0xe0 # 1110 0000
  562 + Utag4 = 0xf0 # 1111 0000
  563 + Utag5 = 0xF8 # 1111 1000
  564 + Umaskx = 0x3f # 0011 1111
  565 + Umask2 = 0x1f # 0001 1111
  566 + Umask3 = 0x0f # 0000 1111
  567 + Umask4 = 0x07 # 0000 0111
  568 + Uchar1max = (1<<7) - 1
  569 + Uchar2max = (1<<11) - 1
  570 + Uchar3max = (1<<16) - 1
  571 + Ucharerr = 0xFFFD # unicode "replacement char"
  572 + Usurrself = 0x10000
  573 + Usurr1 = 0xd800
  574 + Usurr2 = 0xdc00
  575 + Usurr3 = 0xe000
  576 + Umax = 0x10ffff
  577 +
  578 + Spc = ' '[0]
  579 + Unesc = {?b=>?\b, ?f=>?\f, ?n=>?\n, ?r=>?\r, ?t=>?\t}
  580 + Hex = '0123456789abcdef'
  581 +end
1  multi_json.gemspec
@@ -19,7 +19,6 @@ Gem::Specification.new do |s|
19 19 s.add_development_dependency("rake", "~> 0.8")
20 20 s.add_development_dependency("rcov", "~> 0.9")
21 21 s.add_development_dependency("rspec", "~> 2.0")
22   - s.add_development_dependency("activesupport", "~> 3.0")
23 22 s.add_development_dependency("json", "~> 1.4")
24 23 s.add_development_dependency("json_pure", "~> 1.4")
25 24 s.add_development_dependency("yajl-ruby", "~> 0.7")
7 spec/multi_json_spec.rb
@@ -28,7 +28,7 @@ def self.encode(string)
28 28 end
29 29 end
30 30
31   - %w(active_support json_gem json_pure yajl).each do |engine|
  31 + %w(json_gem json_pure okjson yajl).each do |engine|
32 32 context engine do
33 33 before do
34 34 begin
@@ -60,6 +60,11 @@ def self.encode(string)
60 60 end.should raise_error(MultiJson::DecodeError)
61 61 end
62 62
  63 + it 'should stringify symbol keys when encoding' do
  64 + encoded_json = MultiJson.encode(:a => 1, :b => {:c => 2})
  65 + MultiJson.decode(encoded_json).should == {"a" => 1, "b" => {"c" => 2}}
  66 + end
  67 +
63 68 it 'should allow for symbolization of keys' do
64 69 MultiJson.decode('{"abc":{"def":"hgi"}}', :symbolize_keys => true).should == {:abc => {:def => 'hgi'}}
65 70 end

0 comments on commit 4d24e4c

Please sign in to comment.
Something went wrong with that request. Please try again.