xrpl-ruby 0.0.1 → 0.2.4

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,284 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bigdecimal'
4
+ require 'bigdecimal/util'
5
+
6
+ require_relative '../utilities'
7
+
8
+ module BinaryCodec
9
+ class Amount < SerializedType
10
+
11
+ DEFAULT_AMOUNT_HEX = "4000000000000000".freeze
12
+ ZERO_CURRENCY_AMOUNT_HEX = "8000000000000000".freeze
13
+ NATIVE_AMOUNT_BYTE_LENGTH = 8
14
+ CURRENCY_AMOUNT_BYTE_LENGTH = 48
15
+ MAX_IOU_PRECISION = 16
16
+ MIN_IOU_EXPONENT = -96
17
+ MAX_IOU_EXPONENT = 80
18
+
19
+ MAX_DROPS = BigDecimal("1e17")
20
+ MIN_XRP = BigDecimal("1e-6")
21
+
22
+ def initialize(bytes = nil)
23
+ if bytes.nil?
24
+ bytes = hex_to_bytes(DEFAULT_AMOUNT_HEX)
25
+ end
26
+
27
+ @bytes = bytes
28
+ end
29
+
30
+ # Construct an amount from an IOU, MPT, or string amount
31
+ #
32
+ # @param value [Amount, Hash, String] representing the amount
33
+ # @return [Amount] an Amount object
34
+ def self.from(value)
35
+ return value if value.is_a?(Amount)
36
+
37
+ amount = Array.new(8, 0) # Equivalent to a Uint8Array of 8 zeros
38
+
39
+ if value.is_a?(String)
40
+ Amount.assert_xrp_is_valid(value)
41
+
42
+ number = value.to_i # Use to_i for equivalent BigInt handling
43
+
44
+ int_buf = [Array.new(4, 0), Array.new(4, 0)]
45
+ BinaryCodec.write_uint32be(int_buf[0], (number >> 32) & 0xFFFFFFFF, 0)
46
+ BinaryCodec.write_uint32be(int_buf[1], number & 0xFFFFFFFF, 0)
47
+
48
+ amount = int_buf.flatten
49
+
50
+ amount[0] |= 0x40
51
+
52
+ return Amount.new(amount)
53
+ end
54
+
55
+ if is_amount_object_iou?(value)
56
+ number = BigDecimal(value[:value])
57
+ self.assert_iou_is_valid(number)
58
+
59
+ if number.zero?
60
+ amount[0] |= 0x80
61
+ else
62
+ scale = number.frac.to_s('F').split('.').last.size
63
+ unscaled_value = (number * (10**scale)).to_i
64
+ int_string = unscaled_value.abs.to_s.ljust(16, '0')
65
+ num = int_string.to_i
66
+
67
+ int_buf = [Array.new(4, 0), Array.new(4, 0)]
68
+ BinaryCodec.write_uint32be(int_buf[0], (num >> 32) & 0xFFFFFFFF)
69
+ BinaryCodec.write_uint32be(int_buf[1], num & 0xFFFFFFFF)
70
+
71
+ amount = int_buf.flatten
72
+
73
+ amount[0] |= 0x80
74
+
75
+ if number > 0
76
+ amount[0] |= 0x40
77
+ end
78
+
79
+ exponent = number.exponent - 16
80
+ exponent_byte = 97 + exponent
81
+ amount[0] |= exponent_byte >> 2
82
+ amount[1] |= (exponent_byte & 0x03) << 6
83
+ end
84
+
85
+ currency = Currency.from(value[:currency]).to_bytes
86
+ issuer = AccountId.from(value[:issuer]).to_bytes
87
+
88
+ return Amount.new(amount + currency + issuer)
89
+ end
90
+
91
+ end
92
+
93
+ # Read an amount from a BinaryParser
94
+ #
95
+ # @param parser [BinaryParser] The BinaryParser to read the Amount from
96
+ # @return [Amount] An Amount object
97
+ def self.from_parser(parser)
98
+ is_iou = parser.peek & 0x80 != 0
99
+ return Amount.new(parser.read(48)) if is_iou
100
+
101
+ # The amount can be either MPT or XRP at this point
102
+ is_mpt = parser.peek & 0x20 != 0
103
+ num_bytes = is_mpt ? 33 : 8
104
+ Amount.new(parser.read(num_bytes))
105
+ end
106
+
107
+ # The JSON representation of this Amount
108
+ #
109
+ # @return [Hash, String] The JSON interpretation of this.bytes
110
+ def to_json
111
+ if is_native?
112
+ bytes = @bytes.dup # Duplicate the bytes to avoid mutation
113
+ is_positive = (bytes[0] & 0x40) != 0
114
+ sign = is_positive ? '' : '-'
115
+ bytes[0] &= 0x3f
116
+
117
+ msb = BinaryCodec.read_uint32be(bytes[0, 4])
118
+ lsb = BinaryCodec.read_uint32be(bytes[4, 4])
119
+ num = (msb << 32) | lsb
120
+
121
+ return "#{sign}#{num}"
122
+ end
123
+
124
+ if is_iou?
125
+ parser = BinaryParser.new(to_s)
126
+ mantissa = parser.read(8)
127
+ currency = Currency.from_parser(parser)
128
+ issuer = AccountId.from_parser(parser)
129
+
130
+ b1 = mantissa[0]
131
+ b2 = mantissa[1]
132
+
133
+ is_positive = (b1 & 0x40) != 0
134
+ sign = is_positive ? '' : '-'
135
+ exponent = ((b1 & 0x3f) << 2) + ((b2 & 0xff) >> 6) - 97
136
+
137
+ mantissa[0] = 0
138
+ mantissa[1] &= 0x3f
139
+ value = BigDecimal("#{sign}#{bytes_to_hex(mantissa).to_i(16)}") * BigDecimal("1e#{exponent}")
140
+ self.class.assert_iou_is_valid(value)
141
+
142
+ return {
143
+ "value" => value.to_s('F'),
144
+ "currency" => currency.to_json,
145
+ "issuer" => issuer.to_json
146
+ }.to_s
147
+ end
148
+
149
+ if is_mpt?
150
+ parser = BinaryParser.new(to_s)
151
+ leading_byte = parser.read(1)
152
+ amount = parser.read(8)
153
+ mpt_id = Hash192.from_parser(parser)
154
+
155
+ is_positive = (leading_byte[0] & 0x40) != 0
156
+ sign = is_positive ? '' : '-'
157
+
158
+ msb = read_uint32be(amount[0, 4])
159
+ lsb = read_uint32be(amount[4, 4])
160
+ num = (msb << 32) | lsb
161
+
162
+ return {
163
+ value: "#{sign}#{num}",
164
+ mpt_issuance_id: mpt_id.to_s
165
+ }.to_s
166
+ end
167
+
168
+ raise 'Invalid amount to construct JSON'
169
+ end
170
+
171
+ private
172
+
173
+ # Type guard for AmountObjectIOU
174
+ def self.is_amount_object_iou?(arg)
175
+ keys = arg.transform_keys(&:to_s).keys.sort
176
+
177
+ keys.length == 3 &&
178
+ keys[0] == 'currency' &&
179
+ keys[1] == 'issuer' &&
180
+ keys[2] == 'value'
181
+ end
182
+
183
+ # Type guard for AmountObjectMPT
184
+ def self.is_amount_object_mpt?(arg)
185
+ keys = arg.keys.sort
186
+
187
+ keys.length == 2 &&
188
+ keys[0] == 'mpt_issuance_id' &&
189
+ keys[1] == 'value'
190
+ end
191
+
192
+ # Validate XRP amount
193
+ #
194
+ # @param amount [String] representing XRP amount
195
+ # @return [void], but raises an exception if the amount is invalid
196
+ def self.assert_xrp_is_valid(amount)
197
+ if amount.include?('.')
198
+ raise "#{amount} is an illegal amount"
199
+ end
200
+
201
+ decimal = BigDecimal(amount)
202
+ unless decimal.zero?
203
+ if decimal < MIN_XRP || decimal > MAX_DROPS
204
+ raise "#{amount} is an illegal amount"
205
+ end
206
+ end
207
+ end
208
+
209
+ # Validate IOU.value amount
210
+ #
211
+ # @param decimal [BigDecimal] object representing IOU.value
212
+ # @raise [ArgumentError] if the amount is invalid
213
+ def self.assert_iou_is_valid(decimal)
214
+ return if decimal.zero?
215
+
216
+ p = decimal.precision
217
+ e = (decimal.exponent || 0) - 15
218
+
219
+ if p > MAX_IOU_PRECISION || e > MAX_IOU_EXPONENT || e < MIN_IOU_EXPONENT
220
+ raise ArgumentError, 'Decimal precision out of range'
221
+ end
222
+
223
+ verify_no_decimal(decimal)
224
+ end
225
+
226
+ # Validate MPT.value amount
227
+ #
228
+ # @param amount [String] representing MPT.value
229
+ # @return [void], but raises an exception if the amount is invalid
230
+ def self.assert_mpt_is_valid(amount)
231
+ if amount.include?('.')
232
+ raise "#{amount} is an illegal amount"
233
+ end
234
+
235
+ decimal = BigDecimal(amount)
236
+ unless decimal.zero?
237
+ if decimal < BigDecimal("0")
238
+ raise "#{amount} is an illegal amount"
239
+ end
240
+
241
+ if (amount.to_i & mpt_mask) != 0
242
+ raise "#{amount} is an illegal amount"
243
+ end
244
+ end
245
+ end
246
+
247
+ # Ensure that the value, after being multiplied by the exponent, does not
248
+ # contain a decimal. This function is typically used to validate numbers
249
+ # that need to be represented as precise integers after scaling, such as
250
+ # amounts in financial transactions.
251
+ #
252
+ # @param decimal [BigDecimal] A BigDecimal object
253
+ # @raise [ArgumentError] if the value contains a decimal
254
+ # @return [String] The decimal converted to a string without a decimal point
255
+ def self.verify_no_decimal(decimal)
256
+ exponent = -((decimal.exponent || 0) - 16)
257
+ scaled_decimal = decimal * 10 ** exponent
258
+
259
+ raise ArgumentError, 'Decimal place found in int_string' unless scaled_decimal.frac == 0
260
+ end
261
+
262
+ # Check if this amount is in units of Native Currency (XRP)
263
+ #
264
+ # @return [Boolean] true if Native (XRP)
265
+ def is_native?
266
+ (self.bytes[0] & 0x80).zero? && (self.bytes[0] & 0x20).zero?
267
+ end
268
+
269
+ # Check if this amount is in units of MPT
270
+ #
271
+ # @return [Boolean] true if MPT
272
+ def is_mpt?
273
+ (self.bytes[0] & 0x80).zero? && (self.bytes[0] & 0x20) != 0
274
+ end
275
+
276
+ # Check if this amount is in units of IOU
277
+ #
278
+ # @return [Boolean] true if IOU
279
+ def is_iou?
280
+ (self.bytes[0] & 0x80) != 0
281
+ end
282
+
283
+ end
284
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../core/core'
4
+
5
+ module BinaryCodec
6
+ class Blob < SerializedType
7
+
8
+ def initialize(byte_buf = nil)
9
+ @bytes = byte_buf || Array.new(0)
10
+ end
11
+
12
+ def self.from(value)
13
+ return value if value.is_a?(Blob)
14
+
15
+ if value.is_a?(String)
16
+ if value !~ /^[A-F0-9]*$/i
17
+ raise StandardError, 'Cannot construct Blob from a non-hex string'
18
+ end
19
+ return Blob.new(hex_to_bytes(value))
20
+ end
21
+
22
+ raise StandardError, 'Cannot construct Blob from value given'
23
+ end
24
+
25
+ def self.from_parser(parser, hint = nil)
26
+ Blob.new(parser.read(hint))
27
+ end
28
+
29
+ end
30
+
31
+
32
+ end
@@ -0,0 +1,105 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../core/core'
4
+
5
+ module BinaryCodec
6
+ class Currency < Hash160
7
+
8
+ XRP_HEX_REGEX = /^0{40}$/
9
+ ISO_REGEX = /^[A-Z0-9a-z?!@#$%^&*(){}\[\]\|]{3}$/
10
+ HEX_REGEX = /^[A-F0-9]{40}$/
11
+ STANDARD_FORMAT_HEX_REGEX = /^0{24}[\x00-\x7F]{6}0{10}$/
12
+
13
+ attr_reader :iso
14
+
15
+ @width = 20
16
+
17
+ def initialize(byte_buf = nil)
18
+ super(byte_buf || Array.new(20, 0)) # Defaults to XRP bytes if no buffer is given
19
+ hex = bytes_to_hex(@bytes)
20
+
21
+ if XRP_HEX_REGEX.match?(hex)
22
+ @_iso = 'XRP'
23
+ elsif STANDARD_FORMAT_HEX_REGEX.match?(hex)
24
+ @_iso = iso_code_from_hex(@bytes[12..14])
25
+ else
26
+ @_iso = nil
27
+ end
28
+ end
29
+
30
+ def iso
31
+ @_iso
32
+ end
33
+
34
+ def self.from(value)
35
+ return value if value.is_a?(Currency)
36
+
37
+ if value.is_a?(String)
38
+ return Currency.new(bytes_from_representation(value))
39
+ end
40
+
41
+ raise StandardError, 'Cannot construct Currency from value given'
42
+ end
43
+
44
+ def to_json
45
+ iso = self.iso
46
+ return iso unless iso.nil?
47
+
48
+ bytes_to_hex(@bytes)
49
+ end
50
+
51
+ private
52
+
53
+ def iso_code_from_hex(code)
54
+ iso = hex_to_string(bytes_to_hex(code))
55
+ return nil if iso == 'XRP'
56
+ return iso if is_iso_code(iso)
57
+
58
+ nil
59
+ end
60
+
61
+ def is_iso_code(iso)
62
+ !!(ISO_REGEX.match(iso)) # Equivalent to test for regex
63
+ end
64
+
65
+ def self.bytes_from_representation(input)
66
+ unless is_valid_representation(input)
67
+ raise StandardError, "Unsupported Currency representation: #{input}"
68
+ end
69
+
70
+ input.length == 3 ? self.iso_to_bytes(input) : hex_to_bytes(input)
71
+ end
72
+
73
+ def self.iso_to_bytes(iso)
74
+ bytes = Array.new(20, 0) # Equivalent to Uint8Array(20)
75
+ if iso != 'XRP'
76
+ iso_bytes = iso.chars.map(&:ord)
77
+ # Insert iso_bytes at index 12
78
+ bytes[12, iso_bytes.length] = iso_bytes
79
+ end
80
+ bytes
81
+ end
82
+
83
+ def self.is_valid_representation(input)
84
+ if input.is_a?(Array)
85
+ self.is_bytes_array(input)
86
+ else
87
+ self.is_string_representation(input)
88
+ end
89
+ end
90
+
91
+ def self.is_string_representation(input)
92
+ input.length == 3 || is_hex(input)
93
+ end
94
+
95
+ def self.is_bytes_array(bytes)
96
+ bytes.length == 20
97
+ end
98
+
99
+ def self.is_hex(hex)
100
+ !!(HEX_REGEX.match(hex)) # Special case for Vurrency type, do not conflate with valid_hex? function
101
+ end
102
+
103
+ end
104
+
105
+ end
@@ -0,0 +1,105 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BinaryCodec
4
+ class Hash < ComparableSerializedType
5
+
6
+ def initialize(bytes, width)
7
+ @bytes = bytes
8
+ @width = width
9
+ if bytes.length != @width
10
+ # raise StandardError, "Invalid Hash length #{bytes.length} for width #{@width}"
11
+ raise StandardError, "Invalid Hash length #{bytes.length}"
12
+ end
13
+ end
14
+
15
+ def self.from(hex_string)
16
+ new(hex_to_bytes(hex_string))
17
+ end
18
+
19
+ def self.from_parser(parser, hint = nil)
20
+ new(parser.read(hint || width))
21
+ end
22
+
23
+ def compare_to(other)
24
+ @bytes <=> other.bytes
25
+ end
26
+
27
+ # Returns four bits at the specified depth within a hash
28
+ #
29
+ # @param depth [Integer] The depth of the four bits
30
+ # @return [Integer] The number represented by the four bits
31
+ def nibblet(depth)
32
+ byte_index = depth > 0 ? (depth / 2).floor : 0
33
+ b = bytes[byte_index]
34
+ if depth.even?
35
+ (b & 0xf0) >> 4
36
+ else
37
+ b & 0x0f
38
+ end
39
+ end
40
+
41
+ end
42
+
43
+ class Hash128 < Hash
44
+ @width = 16
45
+ @zero_128 = [0] * @width # Array.new(@width, 0)
46
+
47
+ class << self
48
+ attr_reader :width, :zero_128
49
+ end
50
+
51
+ def initialize(bytes = nil)
52
+ bytes = self.class.zero_128 if bytes&.empty?
53
+ super(bytes, self.class.width)
54
+ end
55
+
56
+ def to_hex
57
+ hex = bytes_to_hex(to_bytes)
58
+ return '' if hex.match?(/^0+$/)
59
+ hex
60
+ end
61
+ end
62
+
63
+ class Hash160 < Hash
64
+ @width = 20
65
+ @zero_160 = [0] * @width # Array.new(@width, 0)
66
+
67
+ class << self
68
+ attr_reader :width, :zero_160
69
+ end
70
+
71
+ def initialize(bytes = nil)
72
+ bytes = self.class.zero_160 if bytes&.empty?
73
+ super(bytes, self.class.width)
74
+ end
75
+ end
76
+
77
+ class Hash192 < Hash
78
+ @width = 24
79
+ @zero_192 = [0] * @width # Array.new(@width, 0)
80
+
81
+ class << self
82
+ attr_reader :width, :zero_192
83
+ end
84
+
85
+ def initialize(bytes = nil)
86
+ bytes = self.class.zero_192 if bytes&.empty?
87
+ super(bytes, self.class.width)
88
+ end
89
+ end
90
+
91
+ class Hash256 < Hash
92
+ @width = 32
93
+ @zero_256 = [0] * @width # Array.new(@width, 0)
94
+
95
+ class << self
96
+ attr_reader :width, :zero_256
97
+ end
98
+
99
+ def initialize(bytes = nil)
100
+ bytes = self.class.zero_256 if bytes&.empty?
101
+ super(bytes, self.class.width)
102
+ end
103
+ end
104
+
105
+ end
@@ -0,0 +1,133 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../core/core'
4
+
5
+ module BinaryCodec
6
+ class SerializedType
7
+
8
+ attr_reader :bytes
9
+
10
+ def initialize(bytes = nil)
11
+ raise NotImplementedError, "#{self.class} is an abstract class and cannot be instantiated"
12
+ end
13
+
14
+ def self.from(value)
15
+ raise NotImplementedError, 'from not implemented'
16
+ end
17
+
18
+ def self.from_parser(parser, size_hint = nil)
19
+ raise NotImplementedError, 'from_parser not implemented'
20
+ end
21
+
22
+ # Check if this is needed
23
+ def self.from_json(json)
24
+ raise NotImplementedError, 'from_parser not implemented'
25
+ end
26
+
27
+ # Check if this is needed
28
+ def self.from_hex(hex)
29
+ self.from_bytes(hex_to_bytes(hex))
30
+ end
31
+
32
+ # Check if this is needed
33
+ def self.from_bytes(bytes)
34
+ new(bytes)
35
+ end
36
+
37
+ def to_byte_sink(sink)
38
+ sink.put(to_bytes)
39
+ end
40
+
41
+ # Serialize the given value into bytes
42
+ # This method must be implemented in the subclasses
43
+ # @return [Array<Integer>] - Byte array representing the serialized data
44
+ def to_bytes
45
+ @bytes
46
+ end
47
+
48
+ # Convert to a hex string
49
+ # @return [String] - Hexadecimal representation of the serialized data
50
+ def to_hex
51
+ bytes_to_hex(to_bytes)
52
+ end
53
+
54
+ # Deserialize instance data and convert it to JSON string
55
+ #
56
+ # @param _definitions [Hash] - Definitions for serialization
57
+ # @param _field_name [String] - Field name for serialization
58
+ # @return [String] - JSON representation of the serialized data
59
+ def to_json(_definitions = nil, _field_name = nil)
60
+ to_hex
61
+ end
62
+
63
+ # Represent object as a string (hexadecimal form)
64
+ def to_s
65
+ to_hex
66
+ end
67
+
68
+ def self.get_type_by_name(name)
69
+ type_map = {
70
+ "AccountID" => AccountId,
71
+ "Amount" => Amount,
72
+ "Blob" => Blob,
73
+ "Currency" => Currency,
74
+ "Hash128" => Hash128,
75
+ "Hash160" => Hash160,
76
+ "Hash256" => Hash256,
77
+ #"PathSet" => PathSet,
78
+ #"STArray" => STArray,
79
+ "STObject" => STObject,
80
+ "UInt8" => Uint8,
81
+ "UInt16" => Uint16,
82
+ "UInt32" => Uint32,
83
+ "UInt64" => Uint64,
84
+ #"Vector256" => Vector256
85
+ }
86
+
87
+ unless type_map.key?(name)
88
+ raise "unsupported type #{name}"
89
+ end
90
+
91
+ # Return class
92
+ type_map[name]
93
+ end
94
+
95
+ end
96
+
97
+ class ComparableSerializedType < SerializedType
98
+
99
+ # Compare if `self` is less than `other`
100
+ def lt(other)
101
+ compare_to(other) < 0
102
+ end
103
+
104
+ # Compare if `self` is equal to `other`
105
+ def eq(other)
106
+ compare_to(other) == 0
107
+ end
108
+
109
+ # Compare if `self` is greater than `other`
110
+ def gt(other)
111
+ compare_to(other) > 0
112
+ end
113
+
114
+ # Compare if `self` is greater than or equal to `other`
115
+ def gte(other)
116
+ compare_to(other) >= 0
117
+ end
118
+
119
+ # Compare if `self` is less than or equal to `other`
120
+ def lte(other)
121
+ compare_to(other) <= 0
122
+ end
123
+
124
+ # Overload this method in subclasses to define comparison logic
125
+ #
126
+ # @param other [Object] - The object to compare `self` to
127
+ # @return [Integer] - Returns -1, 0, or 1 depending on the comparison
128
+ def compare_to(other)
129
+ raise NotImplementedError, "Cannot compare #{self} and #{other}"
130
+ end
131
+ end
132
+
133
+ end
@@ -0,0 +1,60 @@
1
+ module BinaryCodec
2
+ class STObject < SerializedType
3
+
4
+ OBJECT_END_MARKER_BYTE = [225]
5
+ OBJECT_END_MARKER = 'ObjectEndMarker'.freeze
6
+ ST_OBJECT = 'STObject'.freeze
7
+ DESTINATION = 'Destination'.freeze
8
+ ACCOUNT = 'Account'.freeze
9
+ SOURCE_TAG = 'SourceTag'.freeze
10
+ DEST_TAG = 'DestinationTag'.freeze
11
+
12
+ # attr_reader :type, :bytes
13
+ #
14
+ def initialize(byte_buf = nil)
15
+ @bytes = byte_buf || Array.new(0)
16
+ end
17
+
18
+ # Construct a STObject from a BinaryParser
19
+ #
20
+ # @param parser [BinaryParser] BinaryParser to read STObject from
21
+ # @param size_hint [Integer] Optional size hint for the object
22
+ # @return [STObject] A STObject object
23
+ def self.from_parser(parser, size_hint = nil)
24
+ list = BytesList.new
25
+ bytes = BinarySerializer.new(list)
26
+
27
+ until parser.end?
28
+ field = parser.read_field
29
+
30
+ break if field.name == OBJECT_END_MARKER
31
+
32
+ associated_value = parser.read_field_value(field)
33
+
34
+ bytes.write_field_and_value(field, associated_value)
35
+ bytes.put(OBJECT_END_MARKER_BYTE) if field.type == ST_OBJECT
36
+ end
37
+
38
+ STObject.new(list.to_bytes)
39
+ end
40
+
41
+ # Method to get the JSON interpretation of self.bytes
42
+ #
43
+ # @return [String] A stringified JSON object
44
+ def to_json()
45
+ parser = BinaryParser.new(to_s)
46
+ accumulator = {}
47
+
48
+ until parser.end?
49
+ field = parser.read_field
50
+ break if field.name == OBJECT_END_MARKER # Break if the object end marker is reached
51
+ value = parser.read_field_value(field).to_json
52
+ value = JSON.parse(value) if field.type == ST_OBJECT || field.type == Amount
53
+ accumulator[field.name] = value
54
+ end
55
+
56
+ JSON.generate(accumulator)
57
+ end
58
+
59
+ end
60
+ end