multi_json 0.0.5 → 1.0.0.rc

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore CHANGED
@@ -23,8 +23,12 @@ log
23
23
  *.gem
24
24
  .bundle
25
25
  pkg
26
+ Gemfile.lock
26
27
 
27
28
  ## RCOV
28
29
  coverage.data
29
30
 
31
+ ## RUBINIUS
32
+ *.rbc
33
+
30
34
  ## PROJECT::SPECIFIC
data/.travis.yml ADDED
@@ -0,0 +1,6 @@
1
+ rvm:
2
+ - 1.8.7
3
+ - 1.9.2
4
+ - ree
5
+ - rbx
6
+ - jruby
data/Gemfile CHANGED
@@ -1,10 +1,7 @@
1
1
  source "http://rubygems.org"
2
2
 
3
- group :development, :test do
4
- gem 'activesupport', '~> 3.0', :require => nil
5
- gem 'json', '~> 1.4', :require => nil
6
- gem 'json_pure', '~> 1.4', :require => nil
7
- gem 'yajl-ruby', '~> 0.7', :require => nil
8
- end
9
-
10
3
  gemspec
4
+
5
+ gem 'json', '~> 1.4', :require => nil
6
+ gem 'json_pure', '~> 1.4', :require => nil
7
+ gem 'yajl-ruby', '~> 0.7', :require => nil, :platforms => :ruby
data/README.rdoc CHANGED
@@ -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.
data/Rakefile CHANGED
@@ -1,25 +1,14 @@
1
1
  require 'rubygems'
2
- require 'bundler'
3
-
4
- Bundler::GemHelper.install_tasks
2
+ begin
3
+ require 'bundler'
4
+ Bundler::GemHelper.install_tasks
5
+ rescue LoadError => e
6
+ puts "although not required, it's recommended that you use bundler during development"
7
+ end
5
8
 
6
9
  require 'rspec/core/rake_task'
7
10
  desc "Run all examples"
8
- RSpec::Core::RakeTask.new(:spec) do |t|
9
- end
10
-
11
- task :cleanup_rcov_files do
12
- rm_rf 'coverage.data'
13
- end
14
-
15
- namespace :spec do
16
- desc "Run all examples using rcov"
17
- RSpec::Core::RakeTask.new :rcov => :cleanup_rcov_files do |t|
18
- t.rcov = true
19
- t.rcov_opts = %[-Ilib -Ispec --exclude "gems/*,features"]
20
- t.rcov_opts << %[--text-report --sort coverage --no-html --aggregate coverage.data]
21
- end
22
- end
11
+ RSpec::Core::RakeTask.new(:spec)
23
12
 
24
13
  task :default => :spec
25
14
 
@@ -30,3 +19,19 @@ Rake::RDocTask.new do |rdoc|
30
19
  rdoc.rdoc_files.include('README*')
31
20
  rdoc.rdoc_files.include('lib/**/*.rb')
32
21
  end
22
+
23
+ task :cleanup_rcov_files do
24
+ rm_rf 'coverage.data'
25
+ end
26
+
27
+ begin
28
+ namespace :spec do
29
+ desc "Run all examples using rcov"
30
+ RSpec::Core::RakeTask.new :rcov => :cleanup_rcov_files do |t|
31
+ t.rcov = true
32
+ t.rcov_opts = %[-Ilib -Ispec --exclude "gems/*,features"]
33
+ t.rcov_opts << %[--text-report --sort coverage --no-html --aggregate coverage.data]
34
+ end
35
+ end
36
+ rescue LoadError
37
+ end
data/lib/multi_json.rb CHANGED
@@ -12,8 +12,8 @@ module MultiJson
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 @@ module MultiJson
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 @@ module MultiJson
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
@@ -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
@@ -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,3 +1,3 @@
1
1
  module MultiJson
2
- VERSION = "0.0.5"
2
+ VERSION = "1.0.0.rc"
3
3
  end
data/multi_json.gemspec CHANGED
@@ -4,24 +4,35 @@ require File.expand_path("../lib/multi_json/version", __FILE__)
4
4
  Gem::Specification.new do |s|
5
5
  s.name = "multi_json"
6
6
  s.version = MultiJson::VERSION
7
+
7
8
  s.required_rubygems_version = Gem::Requirement.new(">= 1.3.6") if s.respond_to? :required_rubygems_version=
9
+
8
10
  s.authors = ["Michael Bleigh"]
9
- s.description = %q{A gem to provide swappable JSON backends utilizing Yajl::Ruby, the JSON gem, ActiveSupport, or JSON pure.}
11
+ s.email = ["michael@intridea.com"]
10
12
  s.summary = %q{A gem to provide swappable JSON backends.}
11
- s.email = ["michael@intridea.com"]
13
+ s.description = %q{A gem to provide swappable JSON backends utilizing Yajl::Ruby, the JSON gem, ActiveSupport, or JSON pure.}
12
14
  s.homepage = "http://github.com/intridea/multi_json"
15
+
13
16
  s.extra_rdoc_files = ["LICENSE", "README.rdoc"]
14
- s.rdoc_options = ["--charset=UTF-8"]
17
+ s.rdoc_options = ["--charset=UTF-8"]
18
+
15
19
  s.executables = `git ls-files -- bin/*`.split("\n").map{|f| File.basename(f)}
16
- s.files = `git ls-files`.split("\n")
17
- s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
20
+ s.files = `git ls-files`.split("\n")
21
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
22
+
18
23
  s.require_paths = ["lib"]
24
+
19
25
  s.add_development_dependency("rake", "~> 0.8")
20
- s.add_development_dependency("rcov", "~> 0.9")
21
26
  s.add_development_dependency("rspec", "~> 2.0")
22
- s.add_development_dependency("activesupport", "~> 3.0")
23
27
  s.add_development_dependency("json", "~> 1.4")
24
28
  s.add_development_dependency("json_pure", "~> 1.4")
25
- s.add_development_dependency("yajl-ruby", "~> 0.7")
29
+
30
+ unless ENV['RUBY_VERSION'].match(/^rbx-/)
31
+ s.add_development_dependency("rcov", "~> 0.9")
32
+ end
33
+
34
+ unless ENV['RUBY_VERSION'].match(/^jruby-/)
35
+ s.add_development_dependency("yajl-ruby", "~> 0.7")
36
+ end
26
37
  end
27
38
 
@@ -1,8 +1,8 @@
1
- require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
1
+ require 'spec_helper'
2
2
 
3
3
  class MockDecoder
4
4
  def self.decode(string, options = {})
5
- {'abc' => 'def'}
5
+ { 'abc' => 'def' }
6
6
  end
7
7
 
8
8
  def self.encode(string)
@@ -13,13 +13,19 @@ end
13
13
  describe "MultiJson" do
14
14
  context 'engines' do
15
15
  it 'should default to the best available gem' do
16
- require 'yajl'
17
- MultiJson.engine.name.should == 'MultiJson::Engines::Yajl'
16
+ # the yajl-ruby gem does not work on jruby, so the best engine is the JsonGem engine
17
+ if ENV['RUBY_VERSION'].match(/^jruby-/)
18
+ require 'json'
19
+ MultiJson.engine.name.should == 'MultiJson::Engines::JsonGem'
20
+ else
21
+ require 'yajl'
22
+ MultiJson.engine.name.should == 'MultiJson::Engines::Yajl'
23
+ end
18
24
  end
19
25
 
20
26
  it 'should be settable via a symbol' do
21
- MultiJson.engine = :yajl
22
- MultiJson.engine.name.should == 'MultiJson::Engines::Yajl'
27
+ MultiJson.engine = :json_gem
28
+ MultiJson.engine.name.should == 'MultiJson::Engines::JsonGem'
23
29
  end
24
30
 
25
31
  it 'should be settable via a class' do
@@ -28,7 +34,7 @@ describe "MultiJson" do
28
34
  end
29
35
  end
30
36
 
31
- %w(active_support json_gem json_pure yajl).each do |engine|
37
+ %w(json_gem json_pure okjson yajl).each do |engine|
32
38
  context engine do
33
39
  before do
34
40
  begin
@@ -41,7 +47,7 @@ describe "MultiJson" do
41
47
  describe '.encode' do
42
48
  it 'should write decodable JSON' do
43
49
  [
44
- {'abc' => 'def'},
50
+ { 'abc' => 'def' },
45
51
  [1, 2, 3, "4"]
46
52
  ].each do |example|
47
53
  MultiJson.decode(MultiJson.encode(example)).should == example
@@ -51,7 +57,7 @@ describe "MultiJson" do
51
57
 
52
58
  describe '.decode' do
53
59
  it 'should properly decode valid JSON' do
54
- MultiJson.decode('{"abc":"def"}').should == {'abc' => 'def'}
60
+ MultiJson.decode('{"abc":"def"}').should == { 'abc' => 'def' }
55
61
  end
56
62
 
57
63
  it 'should raise MultiJson::DecodeError on invalid JSON' do
@@ -60,8 +66,13 @@ describe "MultiJson" do
60
66
  end.should raise_error(MultiJson::DecodeError)
61
67
  end
62
68
 
69
+ it 'should stringify symbol keys when encoding' do
70
+ encoded_json = MultiJson.encode(:a => 1, :b => {:c => 2})
71
+ MultiJson.decode(encoded_json).should == { "a" => 1, "b" => { "c" => 2 } }
72
+ end
73
+
63
74
  it 'should allow for symbolization of keys' do
64
- MultiJson.decode('{"abc":{"def":"hgi"}}', :symbolize_keys => true).should == {:abc => {:def => 'hgi'}}
75
+ MultiJson.decode('{"abc":{"def":"hgi"}}', :symbolize_keys => true).should == { :abc => { :def => 'hgi' } }
65
76
  end
66
77
  end
67
78
  end
data/spec/spec_helper.rb CHANGED
@@ -1,11 +1,9 @@
1
- $LOAD_PATH.unshift(File.dirname(__FILE__))
2
- $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
3
- require 'multi_json'
4
- require 'rspec'
5
- require 'rspec/autorun'
6
1
  begin
7
2
  require 'bundler'
8
- Bundler.setup
9
3
  rescue LoadError
10
- $stderr.puts "Bundler (or a dependency) not available."
4
+ puts "although not required, it's recommended that you use bundler during development"
11
5
  end
6
+
7
+ require 'multi_json'
8
+ require 'rspec'
9
+ require 'rspec/autorun'
metadata CHANGED
@@ -1,13 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: multi_json
3
3
  version: !ruby/object:Gem::Version
4
- hash: 21
5
- prerelease: false
4
+ hash: 7712010
5
+ prerelease: 6
6
6
  segments:
7
+ - 1
7
8
  - 0
8
9
  - 0
9
- - 5
10
- version: 0.0.5
10
+ - rc
11
+ version: 1.0.0.rc
11
12
  platform: ruby
12
13
  authors:
13
14
  - Michael Bleigh
@@ -15,8 +16,7 @@ autorequire:
15
16
  bindir: bin
16
17
  cert_chain: []
17
18
 
18
- date: 2010-11-04 00:00:00 -05:00
19
- default_executable:
19
+ date: 2011-04-14 00:00:00 Z
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency
22
22
  name: rake
@@ -33,25 +33,10 @@ dependencies:
33
33
  version: "0.8"
34
34
  type: :development
35
35
  version_requirements: *id001
36
- - !ruby/object:Gem::Dependency
37
- name: rcov
38
- prerelease: false
39
- requirement: &id002 !ruby/object:Gem::Requirement
40
- none: false
41
- requirements:
42
- - - ~>
43
- - !ruby/object:Gem::Version
44
- hash: 25
45
- segments:
46
- - 0
47
- - 9
48
- version: "0.9"
49
- type: :development
50
- version_requirements: *id002
51
36
  - !ruby/object:Gem::Dependency
52
37
  name: rspec
53
38
  prerelease: false
54
- requirement: &id003 !ruby/object:Gem::Requirement
39
+ requirement: &id002 !ruby/object:Gem::Requirement
55
40
  none: false
56
41
  requirements:
57
42
  - - ~>
@@ -62,26 +47,26 @@ dependencies:
62
47
  - 0
63
48
  version: "2.0"
64
49
  type: :development
65
- version_requirements: *id003
50
+ version_requirements: *id002
66
51
  - !ruby/object:Gem::Dependency
67
- name: activesupport
52
+ name: json
68
53
  prerelease: false
69
- requirement: &id004 !ruby/object:Gem::Requirement
54
+ requirement: &id003 !ruby/object:Gem::Requirement
70
55
  none: false
71
56
  requirements:
72
57
  - - ~>
73
58
  - !ruby/object:Gem::Version
74
59
  hash: 7
75
60
  segments:
76
- - 3
77
- - 0
78
- version: "3.0"
61
+ - 1
62
+ - 4
63
+ version: "1.4"
79
64
  type: :development
80
- version_requirements: *id004
65
+ version_requirements: *id003
81
66
  - !ruby/object:Gem::Dependency
82
- name: json
67
+ name: json_pure
83
68
  prerelease: false
84
- requirement: &id005 !ruby/object:Gem::Requirement
69
+ requirement: &id004 !ruby/object:Gem::Requirement
85
70
  none: false
86
71
  requirements:
87
72
  - - ~>
@@ -92,26 +77,26 @@ dependencies:
92
77
  - 4
93
78
  version: "1.4"
94
79
  type: :development
95
- version_requirements: *id005
80
+ version_requirements: *id004
96
81
  - !ruby/object:Gem::Dependency
97
- name: json_pure
82
+ name: rcov
98
83
  prerelease: false
99
- requirement: &id006 !ruby/object:Gem::Requirement
84
+ requirement: &id005 !ruby/object:Gem::Requirement
100
85
  none: false
101
86
  requirements:
102
87
  - - ~>
103
88
  - !ruby/object:Gem::Version
104
- hash: 7
89
+ hash: 25
105
90
  segments:
106
- - 1
107
- - 4
108
- version: "1.4"
91
+ - 0
92
+ - 9
93
+ version: "0.9"
109
94
  type: :development
110
- version_requirements: *id006
95
+ version_requirements: *id005
111
96
  - !ruby/object:Gem::Dependency
112
97
  name: yajl-ruby
113
98
  prerelease: false
114
- requirement: &id007 !ruby/object:Gem::Requirement
99
+ requirement: &id006 !ruby/object:Gem::Requirement
115
100
  none: false
116
101
  requirements:
117
102
  - - ~>
@@ -122,7 +107,7 @@ dependencies:
122
107
  - 7
123
108
  version: "0.7"
124
109
  type: :development
125
- version_requirements: *id007
110
+ version_requirements: *id006
126
111
  description: A gem to provide swappable JSON backends utilizing Yajl::Ruby, the JSON gem, ActiveSupport, or JSON pure.
127
112
  email:
128
113
  - michael@intridea.com
@@ -137,22 +122,22 @@ files:
137
122
  - .document
138
123
  - .gitignore
139
124
  - .rspec
125
+ - .travis.yml
140
126
  - Gemfile
141
- - Gemfile.lock
142
127
  - LICENSE
143
128
  - README.rdoc
144
129
  - Rakefile
145
130
  - lib/multi_json.rb
146
- - lib/multi_json/engines/active_support.rb
147
131
  - lib/multi_json/engines/json_gem.rb
148
132
  - lib/multi_json/engines/json_pure.rb
133
+ - lib/multi_json/engines/okjson.rb
149
134
  - lib/multi_json/engines/yajl.rb
135
+ - lib/multi_json/vendor/okjson.rb
150
136
  - lib/multi_json/version.rb
151
137
  - multi_json.gemspec
152
138
  - spec/multi_json_spec.rb
153
139
  - spec/spec.opts
154
140
  - spec/spec_helper.rb
155
- has_rdoc: true
156
141
  homepage: http://github.com/intridea/multi_json
157
142
  licenses: []
158
143
 
@@ -184,7 +169,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
184
169
  requirements: []
185
170
 
186
171
  rubyforge_project:
187
- rubygems_version: 1.3.7
172
+ rubygems_version: 1.7.2
188
173
  signing_key:
189
174
  specification_version: 3
190
175
  summary: A gem to provide swappable JSON backends.
@@ -192,3 +177,4 @@ test_files:
192
177
  - spec/multi_json_spec.rb
193
178
  - spec/spec.opts
194
179
  - spec/spec_helper.rb
180
+ has_rdoc:
data/Gemfile.lock DELETED
@@ -1,38 +0,0 @@
1
- PATH
2
- remote: .
3
- specs:
4
- multi_json (0.0.5)
5
-
6
- GEM
7
- remote: http://rubygems.org/
8
- specs:
9
- activesupport (3.0.1)
10
- diff-lcs (1.1.2)
11
- json (1.4.6)
12
- json_pure (1.4.6)
13
- rake (0.8.7)
14
- rcov (0.9.9)
15
- rspec (2.0.0)
16
- rspec-core (= 2.0.0)
17
- rspec-expectations (= 2.0.0)
18
- rspec-mocks (= 2.0.0)
19
- rspec-core (2.0.0)
20
- rspec-expectations (2.0.0)
21
- diff-lcs (>= 1.1.2)
22
- rspec-mocks (2.0.0)
23
- rspec-core (= 2.0.0)
24
- rspec-expectations (= 2.0.0)
25
- yajl-ruby (0.7.8)
26
-
27
- PLATFORMS
28
- ruby
29
-
30
- DEPENDENCIES
31
- activesupport (~> 3.0)
32
- json (~> 1.4)
33
- json_pure (~> 1.4)
34
- multi_json!
35
- rake (~> 0.8)
36
- rcov (~> 0.9)
37
- rspec (~> 2.0)
38
- yajl-ruby (~> 0.7)
@@ -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