55
66import Foundation
77
8- struct JsonRowConverter {
9- let columns : [ String ]
10- let columnTypes : [ ColumnType ]
8+ internal struct JsonRowConverter {
9+ internal let columns : [ String ]
10+ internal let columnTypes : [ ColumnType ]
1111
1212 private static let maxRows = 50_000
1313
@@ -96,12 +96,67 @@ struct JsonRowConverter {
9696 }
9797
9898 private func formatDecimal( _ value: String ) -> String {
99+ // Emit verbatim if already a valid JSON number — preserves full database precision
100+ if isValidJsonNumber ( value) {
101+ return value
102+ }
103+ // Fallback for non-standard formats (e.g., "1.0E5" with leading +)
99104 if let doubleVal = Double ( value) , !doubleVal. isInfinite, !doubleVal. isNaN {
100- return String ( format : " %g " , doubleVal)
105+ return String ( doubleVal)
101106 }
102107 return quotedEscaped ( value)
103108 }
104109
110+ /// Checks whether a string conforms to JSON number grammar (RFC 8259 §6)
111+ private func isValidJsonNumber( _ value: String ) -> Bool {
112+ let scalars = value. unicodeScalars
113+ var iter = scalars. makeIterator ( )
114+ guard var ch = iter. next ( ) else { return false }
115+
116+ // Optional leading minus
117+ if ch == " - " { guard let next = iter. next ( ) else { return false } ; ch = next }
118+
119+ // Integer part: "0" or [1-9][0-9]*
120+ guard ch >= " 0 " && ch <= " 9 " else { return false }
121+ if ch == " 0 " {
122+ // "0" must not be followed by another digit
123+ if let next = iter. next ( ) { ch = next } else { return true }
124+ } else {
125+ while true {
126+ guard let next = iter. next ( ) else { return true }
127+ ch = next
128+ guard ch >= " 0 " && ch <= " 9 " else { break }
129+ }
130+ }
131+
132+ // Optional fractional part
133+ if ch == " . " {
134+ guard let next = iter. next ( ) , next >= " 0 " && next <= " 9 " else { return false }
135+ while true {
136+ guard let next = iter. next ( ) else { return true }
137+ ch = next
138+ guard ch >= " 0 " && ch <= " 9 " else { break }
139+ }
140+ }
141+
142+ // Optional exponent
143+ if ch == " e " || ch == " E " {
144+ guard var next = iter. next ( ) else { return false }
145+ if next == " + " || next == " - " {
146+ guard let signed = iter. next ( ) else { return false }
147+ next = signed
148+ }
149+ guard next >= " 0 " && next <= " 9 " else { return false }
150+ for remaining in IteratorSequence ( iter) {
151+ guard remaining >= " 0 " && remaining <= " 9 " else { return false }
152+ }
153+ } else {
154+ return false // Unexpected trailing character
155+ }
156+
157+ return true
158+ }
159+
105160 private func formatBoolean( _ value: String ) -> String {
106161 switch value. lowercased ( ) {
107162 case " true " , " 1 " , " yes " , " on " :
@@ -114,12 +169,13 @@ struct JsonRowConverter {
114169 }
115170
116171 private func formatJson( _ value: String ) -> String {
117- guard let data = value. data ( using: . utf8) else {
172+ let trimmed = value. trimmingCharacters ( in: . whitespacesAndNewlines)
173+ guard let data = trimmed. data ( using: . utf8) else {
118174 return quotedEscaped ( value)
119175 }
120176 do {
121177 _ = try JSONSerialization . jsonObject ( with: data, options: . fragmentsAllowed)
122- return value
178+ return trimmed
123179 } catch {
124180 return quotedEscaped ( value)
125181 }
0 commit comments