smarter_json 0.5.1
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/.gitignore +46 -0
- data/CHANGELOG.md +70 -0
- data/LICENSE.txt +21 -0
- data/README.md +110 -0
- data/Rakefile +22 -0
- data/docs/_introduction.md +48 -0
- data/docs/basic_read_api.md +72 -0
- data/docs/basic_write_api.md +91 -0
- data/docs/examples.md +140 -0
- data/docs/options.md +58 -0
- data/ext/smarter_json/extconf.rb +30 -0
- data/ext/smarter_json/smarter_json.c +1424 -0
- data/ext/smarter_json/smarter_json.h +9 -0
- data/ext/smarter_json/vendor/ryu.h +819 -0
- data/ext/smarter_json/vendor/ryu.md +22 -0
- data/lib/smarter_json/errors.rb +28 -0
- data/lib/smarter_json/generator.rb +117 -0
- data/lib/smarter_json/parser.rb +926 -0
- data/lib/smarter_json/version.rb +5 -0
- data/lib/smarter_json.rb +24 -0
- metadata +86 -0
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# Ryū, by Ulf Adams
|
|
2
|
+
|
|
3
|
+
- The algorithm is Ryū, by Ulf Adams (Copyright 2018), Apache-2.0 / Boost. Upstream: https://github.com/ulfjack/ryu
|
|
4
|
+
- The actual file you have was vendored from the ruby/json gem, v2.19.7 — path ext/json/ext/vendor/ryu.h. Repo: https://github.com/ruby/json
|
|
5
|
+
|
|
6
|
+
## Update from ruby/json, not from upstream Ryū.
|
|
7
|
+
|
|
8
|
+
The function smarter_json calls — `ryu_s2d_from_parts(m10, m10digits, e10, signedM)` (line 754) — is not in stock upstream Ryū (upstream exposes `s2d/s2d_n`, which take a string). It's ruby/json's adaptation that takes a pre-extracted mantissa/exponent, which is exactly what our fj_decimal_value produces. Pull upstream and you won't have that entry point.
|
|
9
|
+
|
|
10
|
+
## To refresh it:
|
|
11
|
+
|
|
12
|
+
### from the raw file on GitHub (master, or pin to a json release tag):
|
|
13
|
+
`curl -L https://raw.githubusercontent.com/ruby/json/master/ext/json/ext/vendor/ryu.h -o ext/smarter_json/vendor/ryu.h`
|
|
14
|
+
|
|
15
|
+
### or from your local json gem:
|
|
16
|
+
`cp "$(gem contents json | grep ext/json/ext/vendor/ryu.h)" ext/smarter_json/vendor/ryu.h`
|
|
17
|
+
|
|
18
|
+
It was vendored from `ruby/json`:
|
|
19
|
+
- origin: Ryū (`ulfjack/ryu`), adapted by `ruby/json`
|
|
20
|
+
- vendored from: json 2.19.7
|
|
21
|
+
- update command above
|
|
22
|
+
- license: Apache-2.0
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SmarterJSON
|
|
4
|
+
# Single base for everything this gem raises — `rescue SmarterJSON::Error` catches
|
|
5
|
+
# both read (process / process_file) and write (generate) failures. This file is
|
|
6
|
+
# required before parser.rb and generator.rb so the subclasses below can inherit
|
|
7
|
+
# from Error at load time.
|
|
8
|
+
class Error < StandardError; end
|
|
9
|
+
|
|
10
|
+
# Raised by process / process_file on genuinely unparseable input (unterminated
|
|
11
|
+
# string, mismatched bracket, …). Carries the line and column when known.
|
|
12
|
+
class ParseError < Error
|
|
13
|
+
attr_reader :line, :col
|
|
14
|
+
|
|
15
|
+
def initialize(message, line = nil, col = nil)
|
|
16
|
+
@line = line
|
|
17
|
+
@col = col
|
|
18
|
+
super(line && col ? "#{message} at line #{line}, col #{col}" : message)
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Raised when input bytes are invalid for the claimed encoding.
|
|
23
|
+
class EncodingError < ParseError; end
|
|
24
|
+
|
|
25
|
+
# Raised by generate when a value cannot be written as strict JSON (an unsupported
|
|
26
|
+
# type, or a non-finite Float / BigDecimal).
|
|
27
|
+
class GenerateError < Error; end
|
|
28
|
+
end
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "bigdecimal"
|
|
4
|
+
|
|
5
|
+
module SmarterJSON
|
|
6
|
+
module_function
|
|
7
|
+
|
|
8
|
+
# SmarterJSON.generate(obj, options = {}) — write a Ruby value as JSON.
|
|
9
|
+
#
|
|
10
|
+
# options[:format]:
|
|
11
|
+
# :json (default) — standard JSON. Hash -> object, Array -> array,
|
|
12
|
+
# scalar -> scalar. Always valid, interoperable JSON.
|
|
13
|
+
# :ndjson — newline-delimited JSON. An Array writes one element per
|
|
14
|
+
# line; any other value writes as a single line. The
|
|
15
|
+
# inverse of process reading NDJSON back into an Array.
|
|
16
|
+
#
|
|
17
|
+
# Symbol keys/values are emitted as strings; BigDecimal as a JSON number.
|
|
18
|
+
# Unsupported types (Time, custom objects) and non-finite Floats raise
|
|
19
|
+
# SmarterJSON::Error. Returns a String.
|
|
20
|
+
def generate(obj, options = {})
|
|
21
|
+
Generator.new(options).generate(obj)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
class Generator
|
|
25
|
+
ESCAPE = {
|
|
26
|
+
'"' => '\\"', "\\" => "\\\\", "\b" => "\\b", "\f" => "\\f",
|
|
27
|
+
"\n" => "\\n", "\r" => "\\r", "\t" => "\\t"
|
|
28
|
+
}.freeze
|
|
29
|
+
# ", backslash, and control chars 0x00-0x1F must be escaped; everything else
|
|
30
|
+
# (including multi-byte UTF-8) is emitted raw — valid JSON.
|
|
31
|
+
ESCAPE_RE = /["\\\x00-\x1f]/.freeze
|
|
32
|
+
|
|
33
|
+
def initialize(options = {})
|
|
34
|
+
@format = options.fetch(:format, :json)
|
|
35
|
+
unless %i[json ndjson].include?(@format)
|
|
36
|
+
raise ArgumentError, "unknown writer format: #{@format.inspect} (expected :json or :ndjson)"
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def generate(obj)
|
|
41
|
+
buf = +""
|
|
42
|
+
if @format == :ndjson
|
|
43
|
+
if obj.is_a?(Array)
|
|
44
|
+
obj.each do |v|
|
|
45
|
+
emit(v, buf)
|
|
46
|
+
buf << "\n"
|
|
47
|
+
end
|
|
48
|
+
else
|
|
49
|
+
emit(obj, buf)
|
|
50
|
+
buf << "\n"
|
|
51
|
+
end
|
|
52
|
+
else
|
|
53
|
+
emit(obj, buf)
|
|
54
|
+
end
|
|
55
|
+
buf
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
private
|
|
59
|
+
|
|
60
|
+
def emit(obj, buf)
|
|
61
|
+
case obj
|
|
62
|
+
when nil then buf << "null"
|
|
63
|
+
when true then buf << "true"
|
|
64
|
+
when false then buf << "false"
|
|
65
|
+
when String then emit_string(obj, buf)
|
|
66
|
+
when Symbol then emit_string(obj.to_s, buf)
|
|
67
|
+
when Integer then buf << obj.to_s
|
|
68
|
+
when Float then emit_float(obj, buf)
|
|
69
|
+
when BigDecimal then emit_bigdecimal(obj, buf)
|
|
70
|
+
when Array then emit_array(obj, buf)
|
|
71
|
+
when Hash then emit_hash(obj, buf)
|
|
72
|
+
else
|
|
73
|
+
raise SmarterJSON::GenerateError, "SmarterJSON.generate cannot serialize #{obj.class}"
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def emit_array(arr, buf)
|
|
78
|
+
buf << "["
|
|
79
|
+
arr.each_with_index do |v, i|
|
|
80
|
+
buf << "," unless i.zero?
|
|
81
|
+
emit(v, buf)
|
|
82
|
+
end
|
|
83
|
+
buf << "]"
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def emit_hash(hash, buf)
|
|
87
|
+
buf << "{"
|
|
88
|
+
first = true
|
|
89
|
+
hash.each do |k, v|
|
|
90
|
+
buf << "," unless first
|
|
91
|
+
first = false
|
|
92
|
+
emit_string(k.is_a?(String) ? k : k.to_s, buf) # Symbol/other keys -> string
|
|
93
|
+
buf << ":"
|
|
94
|
+
emit(v, buf)
|
|
95
|
+
end
|
|
96
|
+
buf << "}"
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def emit_string(str, buf)
|
|
100
|
+
buf << '"'
|
|
101
|
+
buf << str.gsub(ESCAPE_RE) { |c| ESCAPE[c] || format("\\u%04x", c.ord) }
|
|
102
|
+
buf << '"'
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def emit_float(flt, buf)
|
|
106
|
+
raise SmarterJSON::GenerateError, "SmarterJSON.generate cannot serialize non-finite Float #{flt}" unless flt.finite?
|
|
107
|
+
|
|
108
|
+
buf << flt.to_s # Ruby's Float#to_s is shortest round-trippable; e-notation is valid JSON
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def emit_bigdecimal(num, buf)
|
|
112
|
+
raise SmarterJSON::GenerateError, "SmarterJSON.generate cannot serialize non-finite BigDecimal" unless num.finite?
|
|
113
|
+
|
|
114
|
+
buf << num.to_s("F") # plain decimal notation (BigDecimal's default "0.1e1" is not valid JSON)
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|