multi_json 1.5.0 → 1.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG.md +275 -0
- data/CONTRIBUTING.md +46 -0
- data/LICENSE.md +1 -1
- data/README.md +70 -37
- data/lib/multi_json/adapter.rb +49 -0
- data/lib/multi_json/adapter_error.rb +15 -0
- data/lib/multi_json/adapters/gson.rb +20 -0
- data/lib/multi_json/adapters/jr_jackson.rb +25 -0
- data/lib/multi_json/adapters/json_common.rb +13 -15
- data/lib/multi_json/adapters/json_gem.rb +2 -3
- data/lib/multi_json/adapters/json_pure.rb +2 -3
- data/lib/multi_json/adapters/nsjsonserialization.rb +9 -8
- data/lib/multi_json/adapters/oj.rb +50 -13
- data/lib/multi_json/adapters/ok_json.rb +8 -33
- data/lib/multi_json/adapters/yajl.rb +5 -4
- data/lib/multi_json/convertible_hash_keys.rb +43 -0
- data/lib/multi_json/options.rb +39 -0
- data/lib/multi_json/options_cache.rb +29 -0
- data/lib/multi_json/parse_error.rb +17 -0
- data/lib/multi_json/vendor/okjson.rb +97 -93
- data/lib/multi_json/version.rb +15 -1
- data/lib/multi_json.rb +141 -110
- metadata +44 -81
- data/.document +0 -5
- data/.rspec +0 -3
- data/.travis.yml +0 -10
- data/Gemfile +0 -7
- data/Rakefile +0 -20
- data/multi_json.gemspec +0 -23
- data/spec/adapter_shared_example.rb +0 -127
- data/spec/helper.rb +0 -39
- data/spec/multi_json_spec.rb +0 -100
@@ -1,12 +1,11 @@
|
|
1
|
-
require 'json/pure'
|
1
|
+
require 'json/pure'
|
2
2
|
require 'multi_json/adapters/json_common'
|
3
3
|
|
4
4
|
module MultiJson
|
5
5
|
module Adapters
|
6
6
|
# Use JSON pure to dump/load.
|
7
|
-
class JsonPure
|
7
|
+
class JsonPure < JsonCommon
|
8
8
|
ParseError = ::JSON::ParserError
|
9
|
-
extend JsonCommon
|
10
9
|
end
|
11
10
|
end
|
12
11
|
end
|
@@ -1,3 +1,6 @@
|
|
1
|
+
# This adapter is here for legacy reasons. We can't really test it, so it's hard
|
2
|
+
# to tell if it's even working properly. If you're still using it, please
|
3
|
+
# consider migrating to any other adapter out there.
|
1
4
|
framework 'Foundation'
|
2
5
|
require 'multi_json/adapters/ok_json'
|
3
6
|
|
@@ -6,29 +9,27 @@ module MultiJson
|
|
6
9
|
class Nsjsonserialization < MultiJson::Adapters::OkJson
|
7
10
|
ParseError = ::MultiJson::OkJson::Error
|
8
11
|
|
9
|
-
def
|
10
|
-
string = string.read if string.respond_to?(:read)
|
12
|
+
def load(string, options = {})
|
11
13
|
data = string.dataUsingEncoding(NSUTF8StringEncoding)
|
12
|
-
object = NSJSONSerialization.JSONObjectWithData(data, options
|
14
|
+
object = NSJSONSerialization.JSONObjectWithData(data, :options => NSJSONReadingMutableContainers | NSJSONReadingMutableLeaves, :error => nil)
|
13
15
|
if object
|
14
16
|
object = symbolize_keys(object) if options[:symbolize_keys]
|
15
17
|
object
|
16
18
|
else
|
17
|
-
super(string, options
|
19
|
+
super(string, options)
|
18
20
|
end
|
19
21
|
end
|
20
22
|
|
21
|
-
def
|
23
|
+
def dump(object, options = {})
|
22
24
|
pretty = options[:pretty] ? NSJSONWritingPrettyPrinted : 0
|
23
25
|
object = object.as_json if object.respond_to?(:as_json)
|
24
26
|
if NSJSONSerialization.isValidJSONObject(object)
|
25
|
-
data = NSJSONSerialization.dataWithJSONObject(object, options
|
26
|
-
NSMutableString.alloc.initWithData(data, encoding
|
27
|
+
data = NSJSONSerialization.dataWithJSONObject(object, :options => pretty, :error => nil)
|
28
|
+
NSMutableString.alloc.initWithData(data, :encoding => NSUTF8StringEncoding)
|
27
29
|
else
|
28
30
|
super(object, options)
|
29
31
|
end
|
30
32
|
end
|
31
|
-
|
32
33
|
end
|
33
34
|
end
|
34
35
|
end
|
@@ -1,25 +1,62 @@
|
|
1
|
-
require '
|
1
|
+
require 'set'
|
2
|
+
require 'oj'
|
3
|
+
require 'multi_json/adapter'
|
2
4
|
|
3
5
|
module MultiJson
|
4
6
|
module Adapters
|
5
7
|
# Use the Oj library to dump/load.
|
6
|
-
class Oj
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
8
|
+
class Oj < Adapter
|
9
|
+
defaults :load, :mode => :strict, :symbolize_keys => false
|
10
|
+
defaults :dump, :mode => :compat, :time_format => :ruby, :use_to_json => true
|
11
|
+
|
12
|
+
# In certain cases OJ gem may throw JSON::ParserError exception instead
|
13
|
+
# of its own class. Also, we can't expect ::JSON::ParserError and
|
14
|
+
# ::Oj::ParseError to always be defined, since it's often not the case.
|
15
|
+
# Because of this, we can't reference those classes directly and have to
|
16
|
+
# do string comparison instead. This will not catch subclasses, but it
|
17
|
+
# shouldn't be a problem since the library is not known to be using it
|
18
|
+
# (at least for now).
|
19
|
+
class ParseError < ::SyntaxError
|
20
|
+
WRAPPED_CLASSES = %w[Oj::ParseError JSON::ParserError].to_set.freeze
|
12
21
|
|
13
|
-
|
22
|
+
def self.===(exception)
|
23
|
+
case exception
|
24
|
+
when ::SyntaxError
|
25
|
+
true
|
26
|
+
else
|
27
|
+
WRAPPED_CLASSES.include?(exception.class.to_s)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
14
31
|
|
15
|
-
def
|
16
|
-
options
|
32
|
+
def load(string, options = {})
|
33
|
+
options[:symbol_keys] = options[:symbolize_keys]
|
17
34
|
::Oj.load(string, options)
|
18
35
|
end
|
19
36
|
|
20
|
-
|
21
|
-
|
22
|
-
|
37
|
+
case ::Oj::VERSION
|
38
|
+
when /\A2\./
|
39
|
+
def dump(object, options = {})
|
40
|
+
options.merge!(:indent => 2) if options[:pretty]
|
41
|
+
options[:indent] = options[:indent].to_i if options[:indent]
|
42
|
+
::Oj.dump(object, options)
|
43
|
+
end
|
44
|
+
when /\A3\./
|
45
|
+
PRETTY_STATE_PROTOTYPE = {
|
46
|
+
:indent => " ",
|
47
|
+
:space => " ",
|
48
|
+
:space_before => "",
|
49
|
+
:object_nl => "\n",
|
50
|
+
:array_nl => "\n",
|
51
|
+
:ascii_only => false,
|
52
|
+
}
|
53
|
+
|
54
|
+
def dump(object, options = {})
|
55
|
+
options.merge!(PRETTY_STATE_PROTOTYPE.dup) if options.delete(:pretty)
|
56
|
+
::Oj.dump(object, options)
|
57
|
+
end
|
58
|
+
else
|
59
|
+
fail "Unsupported Oj version: #{::Oj::VERSION}"
|
23
60
|
end
|
24
61
|
end
|
25
62
|
end
|
@@ -1,48 +1,23 @@
|
|
1
|
+
require 'multi_json/adapter'
|
2
|
+
require 'multi_json/convertible_hash_keys'
|
1
3
|
require 'multi_json/vendor/okjson'
|
2
4
|
|
3
5
|
module MultiJson
|
4
6
|
module Adapters
|
5
|
-
class OkJson
|
7
|
+
class OkJson < Adapter
|
8
|
+
include ConvertibleHashKeys
|
6
9
|
ParseError = ::MultiJson::OkJson::Error
|
7
10
|
|
8
|
-
def
|
9
|
-
string = string.read if string.respond_to?(:read)
|
11
|
+
def load(string, options = {})
|
10
12
|
result = ::MultiJson::OkJson.decode("[#{string}]").first
|
11
13
|
options[:symbolize_keys] ? symbolize_keys(result) : result
|
14
|
+
rescue ArgumentError # invalid byte sequence in UTF-8
|
15
|
+
raise ParseError
|
12
16
|
end
|
13
17
|
|
14
|
-
def
|
18
|
+
def dump(object, _ = {})
|
15
19
|
::MultiJson::OkJson.valenc(stringify_keys(object))
|
16
20
|
end
|
17
|
-
|
18
|
-
def self.symbolize_keys(object) #:nodoc:
|
19
|
-
modify_keys(object) do |key|
|
20
|
-
key.is_a?(String) ? key.to_sym : key
|
21
|
-
end
|
22
|
-
end
|
23
|
-
|
24
|
-
def self.stringify_keys(object) #:nodoc:
|
25
|
-
modify_keys(object) do |key|
|
26
|
-
key.respond_to?(:to_s) ? key.to_s : key
|
27
|
-
end
|
28
|
-
end
|
29
|
-
|
30
|
-
def self.modify_keys(object, &modifier) #:nodoc:
|
31
|
-
case object
|
32
|
-
when Array
|
33
|
-
object.map do |value|
|
34
|
-
modify_keys(value, &modifier)
|
35
|
-
end
|
36
|
-
when Hash
|
37
|
-
object.inject({}) do |result, (key, value)|
|
38
|
-
new_key = modifier.call(key)
|
39
|
-
new_value = modify_keys(value, &modifier)
|
40
|
-
result.merge! new_key => new_value
|
41
|
-
end
|
42
|
-
else
|
43
|
-
object
|
44
|
-
end
|
45
|
-
end
|
46
21
|
end
|
47
22
|
end
|
48
23
|
end
|
@@ -1,16 +1,17 @@
|
|
1
|
-
require 'yajl'
|
1
|
+
require 'yajl'
|
2
|
+
require 'multi_json/adapter'
|
2
3
|
|
3
4
|
module MultiJson
|
4
5
|
module Adapters
|
5
6
|
# Use the Yajl-Ruby library to dump/load.
|
6
|
-
class Yajl
|
7
|
+
class Yajl < Adapter
|
7
8
|
ParseError = ::Yajl::ParseError
|
8
9
|
|
9
|
-
def
|
10
|
+
def load(string, options = {})
|
10
11
|
::Yajl::Parser.new(:symbolize_keys => options[:symbolize_keys]).parse(string)
|
11
12
|
end
|
12
13
|
|
13
|
-
def
|
14
|
+
def dump(object, options = {})
|
14
15
|
::Yajl::Encoder.encode(object, options)
|
15
16
|
end
|
16
17
|
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module MultiJson
|
2
|
+
module ConvertibleHashKeys
|
3
|
+
private
|
4
|
+
|
5
|
+
def symbolize_keys(hash)
|
6
|
+
prepare_hash(hash) do |key|
|
7
|
+
key.respond_to?(:to_sym) ? key.to_sym : key
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def stringify_keys(hash)
|
12
|
+
prepare_hash(hash) do |key|
|
13
|
+
key.respond_to?(:to_s) ? key.to_s : key
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def prepare_hash(hash, &key_modifier)
|
18
|
+
return hash unless block_given?
|
19
|
+
case hash
|
20
|
+
when Array
|
21
|
+
hash.map do |value|
|
22
|
+
prepare_hash(value, &key_modifier)
|
23
|
+
end
|
24
|
+
when Hash
|
25
|
+
hash.inject({}) do |result, (key, value)|
|
26
|
+
new_key = key_modifier.call(key)
|
27
|
+
new_value = prepare_hash(value, &key_modifier)
|
28
|
+
result.merge! new_key => new_value
|
29
|
+
end
|
30
|
+
when String, Numeric, true, false, nil
|
31
|
+
hash
|
32
|
+
else
|
33
|
+
if hash.respond_to?(:to_json)
|
34
|
+
hash
|
35
|
+
elsif hash.respond_to?(:to_s)
|
36
|
+
hash.to_s
|
37
|
+
else
|
38
|
+
hash
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module MultiJson
|
2
|
+
module Options
|
3
|
+
def load_options=(options)
|
4
|
+
OptionsCache.reset
|
5
|
+
@load_options = options
|
6
|
+
end
|
7
|
+
|
8
|
+
def dump_options=(options)
|
9
|
+
OptionsCache.reset
|
10
|
+
@dump_options = options
|
11
|
+
end
|
12
|
+
|
13
|
+
def load_options(*args)
|
14
|
+
defined?(@load_options) && get_options(@load_options, *args) || default_load_options
|
15
|
+
end
|
16
|
+
|
17
|
+
def dump_options(*args)
|
18
|
+
defined?(@dump_options) && get_options(@dump_options, *args) || default_dump_options
|
19
|
+
end
|
20
|
+
|
21
|
+
def default_load_options
|
22
|
+
@default_load_options ||= {}
|
23
|
+
end
|
24
|
+
|
25
|
+
def default_dump_options
|
26
|
+
@default_dump_options ||= {}
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def get_options(options, *args)
|
32
|
+
if options.respond_to?(:call) && options.arity
|
33
|
+
options.arity == 0 ? options[] : options[*args]
|
34
|
+
elsif options.respond_to?(:to_hash)
|
35
|
+
options.to_hash
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module MultiJson
|
2
|
+
module OptionsCache
|
3
|
+
extend self
|
4
|
+
|
5
|
+
def reset
|
6
|
+
@dump_cache = {}
|
7
|
+
@load_cache = {}
|
8
|
+
end
|
9
|
+
|
10
|
+
def fetch(type, key, &block)
|
11
|
+
cache = instance_variable_get("@#{type}_cache")
|
12
|
+
cache.key?(key) ? cache[key] : write(cache, key, &block)
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
# Normally MultiJson is used with a few option sets for both dump/load
|
18
|
+
# methods. When options are generated dynamically though, every call would
|
19
|
+
# cause a cache miss and the cache would grow indefinitely. To prevent
|
20
|
+
# this, we just reset the cache every time the number of keys outgrows
|
21
|
+
# 1000.
|
22
|
+
MAX_CACHE_SIZE = 1000
|
23
|
+
|
24
|
+
def write(cache, key)
|
25
|
+
cache.clear if cache.length >= MAX_CACHE_SIZE
|
26
|
+
cache[key] = yield
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module MultiJson
|
2
|
+
class ParseError < StandardError
|
3
|
+
attr_reader :data, :cause
|
4
|
+
|
5
|
+
def self.build(original_exception, data)
|
6
|
+
new(original_exception.message).tap do |exception|
|
7
|
+
exception.instance_eval do
|
8
|
+
@cause = original_exception
|
9
|
+
set_backtrace original_exception.backtrace
|
10
|
+
@data = data
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
DecodeError = LoadError = ParseError # Legacy support
|
17
|
+
end
|
@@ -24,11 +24,12 @@
|
|
24
24
|
|
25
25
|
require 'stringio'
|
26
26
|
|
27
|
-
# Some parts adapted from
|
28
|
-
# http://golang.org/src/pkg/json/decode.go and
|
29
|
-
# http://golang.org/src/pkg/utf8/utf8.go
|
30
27
|
module MultiJson
|
28
|
+
# Some parts adapted from
|
29
|
+
# https://golang.org/src/encoding/json/decode.go and
|
30
|
+
# https://golang.org/src/unicode/utf8/utf8.go
|
31
31
|
module OkJson
|
32
|
+
Upstream = '45'
|
32
33
|
extend self
|
33
34
|
|
34
35
|
|
@@ -50,12 +51,53 @@ module MultiJson
|
|
50
51
|
end
|
51
52
|
|
52
53
|
|
54
|
+
# Encodes x into a json text. It may contain only
|
55
|
+
# Array, Hash, String, Numeric, true, false, nil.
|
56
|
+
# (Note, this list excludes Symbol.)
|
57
|
+
# X itself must be an Array or a Hash.
|
58
|
+
# No other value can be encoded, and an error will
|
59
|
+
# be raised if x contains any other value, such as
|
60
|
+
# Nan, Infinity, Symbol, and Proc, or if a Hash key
|
61
|
+
# is not a String.
|
62
|
+
# Strings contained in x must be valid UTF-8.
|
63
|
+
def encode(x)
|
64
|
+
case x
|
65
|
+
when Hash then objenc(x)
|
66
|
+
when Array then arrenc(x)
|
67
|
+
else
|
68
|
+
raise Error, 'root value must be an Array or a Hash'
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
|
73
|
+
def valenc(x)
|
74
|
+
case x
|
75
|
+
when Hash then objenc(x)
|
76
|
+
when Array then arrenc(x)
|
77
|
+
when String then strenc(x)
|
78
|
+
when Numeric then numenc(x)
|
79
|
+
when true then "true"
|
80
|
+
when false then "false"
|
81
|
+
when nil then "null"
|
82
|
+
else
|
83
|
+
if x.respond_to?(:to_json)
|
84
|
+
x.to_json
|
85
|
+
else
|
86
|
+
raise Error, "cannot encode #{x.class}: #{x.inspect}"
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
|
92
|
+
private
|
93
|
+
|
94
|
+
|
53
95
|
# Parses a "json text" in the sense of RFC 4627.
|
54
96
|
# Returns the parsed value and any trailing tokens.
|
55
97
|
# Note: this is almost the same as valparse,
|
56
98
|
# except that it does not accept atomic values.
|
57
99
|
def textparse(ts)
|
58
|
-
if ts.length
|
100
|
+
if ts.length <= 0
|
59
101
|
raise Error, 'empty'
|
60
102
|
end
|
61
103
|
|
@@ -72,7 +114,7 @@ module MultiJson
|
|
72
114
|
# Parses a "value" in the sense of RFC 4627.
|
73
115
|
# Returns the parsed value and any trailing tokens.
|
74
116
|
def valparse(ts)
|
75
|
-
if ts.length
|
117
|
+
if ts.length <= 0
|
76
118
|
raise Error, 'empty'
|
77
119
|
end
|
78
120
|
|
@@ -201,21 +243,19 @@ module MultiJson
|
|
201
243
|
# it is the lexeme.
|
202
244
|
def tok(s)
|
203
245
|
case s[0]
|
204
|
-
when ?{
|
205
|
-
when ?}
|
206
|
-
when ?:
|
207
|
-
when ?,
|
208
|
-
when ?[
|
209
|
-
when ?]
|
210
|
-
when ?n
|
211
|
-
when ?t
|
212
|
-
when ?f
|
213
|
-
when ?"
|
214
|
-
when Spc then [:space, s[0,1], s[0,1]]
|
215
|
-
|
216
|
-
|
217
|
-
when ?\r then [:space, s[0,1], s[0,1]]
|
218
|
-
else numtok(s)
|
246
|
+
when ?{ then ['{', s[0,1], s[0,1]]
|
247
|
+
when ?} then ['}', s[0,1], s[0,1]]
|
248
|
+
when ?: then [':', s[0,1], s[0,1]]
|
249
|
+
when ?, then [',', s[0,1], s[0,1]]
|
250
|
+
when ?[ then ['[', s[0,1], s[0,1]]
|
251
|
+
when ?] then [']', s[0,1], s[0,1]]
|
252
|
+
when ?n then nulltok(s)
|
253
|
+
when ?t then truetok(s)
|
254
|
+
when ?f then falsetok(s)
|
255
|
+
when ?" then strtok(s)
|
256
|
+
when Spc, ?\t, ?\n, ?\r then [:space, s[0,1], s[0,1]]
|
257
|
+
else
|
258
|
+
numtok(s)
|
219
259
|
end
|
220
260
|
end
|
221
261
|
|
@@ -226,14 +266,14 @@ module MultiJson
|
|
226
266
|
|
227
267
|
|
228
268
|
def numtok(s)
|
229
|
-
m =
|
269
|
+
m = /(-?(?:[1-9][0-9]+|[0-9]))([.][0-9]+)?([eE][+-]?[0-9]+)?/.match(s)
|
230
270
|
if m && m.begin(0) == 0
|
231
|
-
if m[
|
232
|
-
[:val, m[0], Integer(m[
|
271
|
+
if !m[2] && !m[3]
|
272
|
+
[:val, m[0], Integer(m[0])]
|
233
273
|
elsif m[2]
|
234
274
|
[:val, m[0], Float(m[0])]
|
235
275
|
else
|
236
|
-
[:val, m[0], Integer(m[
|
276
|
+
[:val, m[0], Integer(m[1])*(10**m[3][1..-1].to_i(10))]
|
237
277
|
end
|
238
278
|
else
|
239
279
|
[]
|
@@ -265,17 +305,14 @@ module MultiJson
|
|
265
305
|
def unquote(q)
|
266
306
|
q = q[1...-1]
|
267
307
|
a = q.dup # allocate a big enough string
|
268
|
-
rubydoesenc = false
|
269
308
|
# In ruby >= 1.9, a[w] is a codepoint, not a byte.
|
270
|
-
if
|
309
|
+
if rubydoesenc?
|
271
310
|
a.force_encoding('UTF-8')
|
272
|
-
rubydoesenc = true
|
273
311
|
end
|
274
312
|
r, w = 0, 0
|
275
313
|
while r < q.length
|
276
314
|
c = q[r]
|
277
|
-
|
278
|
-
when c == ?\\
|
315
|
+
if c == ?\\
|
279
316
|
r += 1
|
280
317
|
if r >= q.length
|
281
318
|
raise Error, "string literal ends with a \"\\\": \"#{q}\""
|
@@ -308,7 +345,7 @@ module MultiJson
|
|
308
345
|
end
|
309
346
|
end
|
310
347
|
end
|
311
|
-
if rubydoesenc
|
348
|
+
if rubydoesenc?
|
312
349
|
a[w] = '' << uchar
|
313
350
|
w += 1
|
314
351
|
else
|
@@ -317,7 +354,7 @@ module MultiJson
|
|
317
354
|
else
|
318
355
|
raise Error, "invalid escape char #{q[r]} in \"#{q}\""
|
319
356
|
end
|
320
|
-
|
357
|
+
elsif c == ?" || c < Spc
|
321
358
|
raise Error, "invalid character in string literal \"#{q}\""
|
322
359
|
else
|
323
360
|
# Copy anything else byte-for-byte.
|
@@ -338,15 +375,14 @@ module MultiJson
|
|
338
375
|
# bytes in string a at position i.
|
339
376
|
# Returns the number of bytes written.
|
340
377
|
def ucharenc(a, i, u)
|
341
|
-
|
342
|
-
when u <= Uchar1max
|
378
|
+
if u <= Uchar1max
|
343
379
|
a[i] = (u & 0xff).chr
|
344
380
|
1
|
345
|
-
|
381
|
+
elsif u <= Uchar2max
|
346
382
|
a[i+0] = (Utag2 | ((u>>6)&0xff)).chr
|
347
383
|
a[i+1] = (Utagx | (u&Umaskx)).chr
|
348
384
|
2
|
349
|
-
|
385
|
+
elsif u <= Uchar3max
|
350
386
|
a[i+0] = (Utag3 | ((u>>12)&0xff)).chr
|
351
387
|
a[i+1] = (Utagx | ((u>>6)&Umaskx)).chr
|
352
388
|
a[i+2] = (Utagx | (u&Umaskx)).chr
|
@@ -383,54 +419,15 @@ module MultiJson
|
|
383
419
|
|
384
420
|
|
385
421
|
def nibble(c)
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
when ?A <= c && c <= ?Z then c.ord - ?A.ord + 10
|
422
|
+
if ?0 <= c && c <= ?9 then c.ord - ?0.ord
|
423
|
+
elsif ?a <= c && c <= ?z then c.ord - ?a.ord + 10
|
424
|
+
elsif ?A <= c && c <= ?Z then c.ord - ?A.ord + 10
|
390
425
|
else
|
391
426
|
raise Error, "invalid hex code #{c}"
|
392
427
|
end
|
393
428
|
end
|
394
429
|
|
395
430
|
|
396
|
-
# Encodes x into a json text. It may contain only
|
397
|
-
# Array, Hash, String, Numeric, true, false, nil.
|
398
|
-
# (Note, this list excludes Symbol.)
|
399
|
-
# X itself must be an Array or a Hash.
|
400
|
-
# No other value can be encoded, and an error will
|
401
|
-
# be raised if x contains any other value, such as
|
402
|
-
# Nan, Infinity, Symbol, and Proc, or if a Hash key
|
403
|
-
# is not a String.
|
404
|
-
# Strings contained in x must be valid UTF-8.
|
405
|
-
def encode(x)
|
406
|
-
case x
|
407
|
-
when Hash then objenc(x)
|
408
|
-
when Array then arrenc(x)
|
409
|
-
else
|
410
|
-
raise Error, 'root value must be an Array or a Hash'
|
411
|
-
end
|
412
|
-
end
|
413
|
-
|
414
|
-
|
415
|
-
def valenc(x)
|
416
|
-
case x
|
417
|
-
when Hash then objenc(x)
|
418
|
-
when Array then arrenc(x)
|
419
|
-
when String then strenc(x)
|
420
|
-
when Numeric then numenc(x)
|
421
|
-
when true then "true"
|
422
|
-
when false then "false"
|
423
|
-
when nil then "null"
|
424
|
-
else
|
425
|
-
if x.respond_to?(:to_json)
|
426
|
-
x.to_json
|
427
|
-
else
|
428
|
-
raise Error, "cannot encode #{x.class}: #{x.inspect}"
|
429
|
-
end
|
430
|
-
end
|
431
|
-
end
|
432
|
-
|
433
|
-
|
434
431
|
def objenc(x)
|
435
432
|
'{' + x.map{|k,v| keyenc(k) + ':' + valenc(v)}.join(',') + '}'
|
436
433
|
end
|
@@ -455,9 +452,6 @@ module MultiJson
|
|
455
452
|
t.putc(?")
|
456
453
|
r = 0
|
457
454
|
|
458
|
-
# In ruby >= 1.9, s[r] is a codepoint, not a byte.
|
459
|
-
rubydoesenc = s.class.method_defined?(:encoding)
|
460
|
-
|
461
455
|
while r < s.length
|
462
456
|
case s[r]
|
463
457
|
when ?" then t.print('\\"')
|
@@ -469,15 +463,20 @@ module MultiJson
|
|
469
463
|
when ?\t then t.print('\\t')
|
470
464
|
else
|
471
465
|
c = s[r]
|
472
|
-
|
473
|
-
|
466
|
+
# In ruby >= 1.9, s[r] is a codepoint, not a byte.
|
467
|
+
if rubydoesenc?
|
474
468
|
begin
|
475
|
-
c.ord
|
469
|
+
# c.ord will raise an error if c is invalid UTF-8
|
470
|
+
if c.ord < Spc.ord
|
471
|
+
c = "\\u%04x" % [c.ord]
|
472
|
+
end
|
476
473
|
t.write(c)
|
477
474
|
rescue
|
478
475
|
t.write(Ustrerr)
|
479
476
|
end
|
480
|
-
|
477
|
+
elsif c < Spc
|
478
|
+
t.write("\\u%04x" % c)
|
479
|
+
elsif Spc <= c && c <= ?~
|
481
480
|
t.putc(c)
|
482
481
|
else
|
483
482
|
n = ucharcopy(t, s, r) # ensure valid UTF-8 output
|
@@ -569,6 +568,11 @@ module MultiJson
|
|
569
568
|
end
|
570
569
|
|
571
570
|
|
571
|
+
def rubydoesenc?
|
572
|
+
::String.method_defined?(:force_encoding)
|
573
|
+
end
|
574
|
+
|
575
|
+
|
572
576
|
class Utf8Error < ::StandardError
|
573
577
|
end
|
574
578
|
|
@@ -577,15 +581,15 @@ module MultiJson
|
|
577
581
|
end
|
578
582
|
|
579
583
|
|
580
|
-
Utagx =
|
581
|
-
Utag2 =
|
582
|
-
Utag3 =
|
583
|
-
Utag4 =
|
584
|
-
Utag5 =
|
585
|
-
Umaskx =
|
586
|
-
Umask2 =
|
587
|
-
Umask3 =
|
588
|
-
Umask4 =
|
584
|
+
Utagx = 0b1000_0000
|
585
|
+
Utag2 = 0b1100_0000
|
586
|
+
Utag3 = 0b1110_0000
|
587
|
+
Utag4 = 0b1111_0000
|
588
|
+
Utag5 = 0b1111_1000
|
589
|
+
Umaskx = 0b0011_1111
|
590
|
+
Umask2 = 0b0001_1111
|
591
|
+
Umask3 = 0b0000_1111
|
592
|
+
Umask4 = 0b0000_0111
|
589
593
|
Uchar1max = (1<<7) - 1
|
590
594
|
Uchar2max = (1<<11) - 1
|
591
595
|
Uchar3max = (1<<16) - 1
|
data/lib/multi_json/version.rb
CHANGED
@@ -1,3 +1,17 @@
|
|
1
1
|
module MultiJson
|
2
|
-
|
2
|
+
class Version
|
3
|
+
MAJOR = 1 unless defined? MultiJson::Version::MAJOR
|
4
|
+
MINOR = 15 unless defined? MultiJson::Version::MINOR
|
5
|
+
PATCH = 0 unless defined? MultiJson::Version::PATCH
|
6
|
+
PRE = nil unless defined? MultiJson::Version::PRE
|
7
|
+
|
8
|
+
class << self
|
9
|
+
# @return [String]
|
10
|
+
def to_s
|
11
|
+
[MAJOR, MINOR, PATCH, PRE].compact.join('.')
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
VERSION = Version.to_s.freeze
|
3
17
|
end
|