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.
@@ -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