@@ -73,23 +73,9 @@ struct BsonDocumentFlattener {
7373 case let str as String :
7474 return str
7575 case let num as NSNumber :
76- // Check if it's a boolean (NSNumber wraps booleans too)
77- if CFBooleanGetTypeID ( ) == CFGetTypeID ( num) {
78- return num. boolValue ? " true " : " false "
79- }
80- return num. stringValue
81- case let int as Int :
82- return String ( int)
83- case let int32 as Int32 :
84- return String ( int32)
85- case let int64 as Int64 :
86- return String ( int64)
87- case let double as Double :
88- return String ( double)
89- case let bool as Bool :
90- return bool ? " true " : " false "
76+ return displayString ( for: num)
9177 case let date as Date :
92- return ISO8601DateFormatter ( ) . string ( from: date)
78+ return iso8601Formatter . string ( from: date)
9379 case let data as Data :
9480 return formatBinaryData ( data)
9581 case let dict as [ String : Any ] :
@@ -121,24 +107,20 @@ struct BsonDocumentFlattener {
121107 /// Serialize a dictionary or array to compact JSON string
122108 static func serializeToJson( _ value: Any ) -> String {
123109 let sanitized = sanitizeForJson ( value)
124- do {
125- let data = try JSONSerialization . data ( withJSONObject: sanitized, options: [ . sortedKeys] )
126- if let json = String ( data: data, encoding: . utf8) {
127- // Cap at 10k chars to prevent mega-document display issues
128- let nsJson = json as NSString
129- if nsJson. length > 10_000 {
130- return String ( json. prefix ( 10_000 ) ) + " ... "
131- }
132- return json
133- }
134- } catch {
135- // Fall through to description
110+ guard JSONSerialization . isValidJSONObject ( sanitized) ,
111+ let data = try ? JSONSerialization . data ( withJSONObject: sanitized, options: [ . sortedKeys] ) ,
112+ let json = String ( data: data, encoding: . utf8) else {
113+ return String ( describing: value)
136114 }
137- return String ( describing: value)
115+ let nsJson = json as NSString
116+ if nsJson. length > 10_000 {
117+ return String ( json. prefix ( 10_000 ) ) + " ... "
118+ }
119+ return json
138120 }
139121
140- /// Recursively convert non-JSON-safe types (Data, Date, etc.) to JSON-safe representations
141- private static func sanitizeForJson( _ value: Any ) -> Any {
122+ /// Recursively convert every value into a JSON-safe representation
123+ static func sanitizeForJson( _ value: Any ) -> Any {
142124 switch value {
143125 case let dict as [ String : Any ] :
144126 return dict. mapValues { sanitizeForJson ( $0) }
@@ -147,12 +129,50 @@ struct BsonDocumentFlattener {
147129 case let data as Data :
148130 return formatBinaryData ( data)
149131 case let date as Date :
150- return ISO8601DateFormatter ( ) . string ( from: date)
151- default :
132+ return iso8601Formatter . string ( from: date)
133+ case is NSNull :
152134 return value
135+ case let str as String :
136+ return str
137+ case let num as NSNumber :
138+ return sanitizeNumber ( num)
139+ default :
140+ return String ( describing: value)
153141 }
154142 }
155143
144+ private static let iso8601Formatter = ISO8601DateFormatter ( )
145+
146+ private static func displayString( for num: NSNumber ) -> String {
147+ if isBoolean ( num) {
148+ return num. boolValue ? " true " : " false "
149+ }
150+ if isFloatingPoint ( num) , !num. doubleValue. isFinite {
151+ return nonFiniteToken ( num. doubleValue)
152+ }
153+ return num. stringValue
154+ }
155+
156+ private static func sanitizeNumber( _ num: NSNumber ) -> Any {
157+ guard !isBoolean( num) else { return num }
158+ guard isFloatingPoint ( num) , !num. doubleValue. isFinite else { return num }
159+ return nonFiniteToken ( num. doubleValue)
160+ }
161+
162+ private static func isBoolean( _ num: NSNumber ) -> Bool {
163+ CFBooleanGetTypeID ( ) == CFGetTypeID ( num)
164+ }
165+
166+ private static func isFloatingPoint( _ num: NSNumber ) -> Bool {
167+ let objCType = String ( cString: num. objCType)
168+ return objCType == " d " || objCType == " f "
169+ }
170+
171+ private static func nonFiniteToken( _ value: Double ) -> String {
172+ if value. isNaN { return " NaN " }
173+ return value > 0 ? " Infinity " : " -Infinity "
174+ }
175+
156176 /// Format binary data: 16-byte values as UUID, otherwise as hex string
157177 private static func formatBinaryData( _ data: Data ) -> String {
158178 if data. count == 16 {
@@ -193,27 +213,16 @@ struct BsonDocumentFlattener {
193213
194214 switch value {
195215 case let num as NSNumber :
196- if CFBooleanGetTypeID ( ) == CFGetTypeID ( num) {
216+ if isBoolean ( num) {
197217 return 8 // Boolean
198218 }
199- let objCType = String ( cString: num. objCType)
200- if objCType == " d " || objCType == " f " {
219+ if isFloatingPoint ( num) {
201220 return 1 // Double
202221 }
203- if objCType == " q " || objCType == " l " {
204- return 18 // Int64
205- }
206- return 16 // Int32
222+ let objCType = String ( cString: num. objCType)
223+ return objCType == " q " || objCType == " l " ? 18 : 16 // Int64 : Int32
207224 case is String :
208225 return 2 // String
209- case is Bool :
210- return 8 // Boolean
211- case is Int , is Int32 :
212- return 16 // Int32
213- case is Int64 :
214- return 18 // Int64
215- case is Double , is Float :
216- return 1 // Double
217226 case is Date :
218227 return 9 // Date
219228 case is Data :
0 commit comments