@@ -816,66 +816,145 @@ private struct JSONReader {
816816 }
817817
818818 //MARK: - Number parsing
819- static let numberCodePoints : [ UInt8 ] = [
820- 0x30 , 0x31 , 0x32 , 0x33 , 0x34 , 0x35 , 0x36 , 0x37 , 0x38 , 0x39 , // 0...9
821- 0x2E , 0x2D , 0x2B , 0x45 , 0x65 , // . - + E e
822- ]
819+ private static let ZERO = UInt8 ( ascii: " 0 " )
820+ private static let ONE = UInt8 ( ascii: " 1 " )
821+ private static let NINE = UInt8 ( ascii: " 9 " )
822+ private static let MINUS = UInt8 ( ascii: " - " )
823+ private static let PLUS = UInt8 ( ascii: " + " )
824+ private static let LOWER_EXPONENT = UInt8 ( ascii: " e " )
825+ private static let UPPER_EXPONENT = UInt8 ( ascii: " E " )
826+ private static let DECIMAL_SEPARATOR = UInt8 ( ascii: " . " )
827+ private static let allDigits = ( ZERO... NINE)
828+ private static let oneToNine = ( ONE... NINE)
829+
830+ private static let numberCodePoints : [ UInt8 ] = {
831+ var numberCodePoints = Array ( ZERO... NINE)
832+ numberCodePoints. append ( contentsOf: [ DECIMAL_SEPARATOR, MINUS, PLUS, LOWER_EXPONENT, UPPER_EXPONENT] )
833+ return numberCodePoints
834+ } ( )
835+
823836
824837 func parseNumber( _ input: Index , options opt: JSONSerialization . ReadingOptions ) throws -> ( Any , Index ) ? {
825- let ZERO = UInt8 ( ascii: " 0 " )
826- let ONE = UInt8 ( ascii: " 1 " )
827- let NINE = UInt8 ( ascii: " 9 " )
828- let MINUS = UInt8 ( ascii: " - " )
829838
830839 var isNegative = false
831840 var string = " "
832-
833- // Validate the first few characters look like a JSON encoded number:
834- // Optional '-' sign at start only 1 leading zero if followed by a decimal point.
841+ var isInteger = true
842+ var exponent = 0
843+ var positiveExponent = true
835844 var index = input
836- func nextASCII( ) -> UInt8 ? {
837- guard let ( ascii, nextIndex) = source. takeASCII ( index) ,
838- JSONReader . numberCodePoints. contains ( ascii) else { return nil }
839- index = nextIndex
840- return ascii
841- }
845+ var digitCount : Int ?
846+ var ascii : UInt8 = 0 // set by nextASCII()
847+
848+ // Validate the input is a valid JSON number, also gather the following
849+ // about the input: isNegative, isInteger, the exponent and if it is +/-,
850+ // and finally the count of digits including excluding an '.'
851+ func checkJSONNumber( ) throws -> Bool {
852+ // Return true if the next character is any one of the valid JSON number characters
853+ func nextASCII( ) -> Bool {
854+ guard let ( ch, nextIndex) = source. takeASCII ( index) ,
855+ JSONReader . numberCodePoints. contains ( ch) else { return false }
856+
857+ index = nextIndex
858+ ascii = ch
859+ string. append ( Character ( UnicodeScalar ( ascii) ) )
860+ return true
861+ }
842862
843- guard var ascii = nextASCII ( ) else { return nil }
844- guard ascii == MINUS || ( ascii >= ZERO && ascii <= NINE) else { return nil }
845- if ascii == MINUS {
846- string = " - "
847- isNegative = true
848- guard let d = nextASCII ( ) else { return nil }
849- ascii = d
850- }
863+ // Consume as many digits as possible and return with the next non-digit
864+ // or nil if end of string.
865+ func readDigits( ) -> UInt8 ? {
866+ while let ( ch, nextIndex) = source. takeASCII ( index) {
867+ if !JSONReader. allDigits. contains ( ch) {
868+ return ch
869+ }
870+ string. append ( Character ( UnicodeScalar ( ch) ) )
871+ index = nextIndex
872+ }
873+ return nil
874+ }
875+
876+ guard nextASCII ( ) else { return false }
877+
878+ if ascii == JSONReader . MINUS {
879+ isNegative = true
880+ guard nextASCII ( ) else { return false }
881+ }
882+
883+ if JSONReader . oneToNine. contains ( ascii) {
884+ guard let ch = readDigits ( ) else { return true }
885+ ascii = ch
886+ if [ JSONReader . DECIMAL_SEPARATOR, JSONReader . LOWER_EXPONENT, JSONReader . UPPER_EXPONENT ] . contains ( ascii) {
887+ guard nextASCII ( ) else { return false } // There should be at least one char as readDigits didnt remove the '.eE'
888+ }
889+ } else if ascii == JSONReader . ZERO {
890+ guard nextASCII ( ) else { return true }
891+ } else {
892+ throw NSError ( domain: NSCocoaErrorDomain, code: CocoaError . propertyListReadCorrupt. rawValue,
893+ userInfo: [ " NSDebugDescription " : " Numbers must start with a 1-9 at character \( input) . " ] )
894+ }
895+
896+ if ascii == JSONReader . DECIMAL_SEPARATOR {
897+ isInteger = false
898+ guard readDigits ( ) != nil else { return true }
899+ guard nextASCII ( ) else { return true }
900+ } else if JSONReader . allDigits. contains ( ascii) {
901+ throw NSError ( domain: NSCocoaErrorDomain, code: CocoaError . propertyListReadCorrupt. rawValue,
902+ userInfo: [ " NSDebugDescription " : " Leading zeros not allowed at character \( input) . " ] )
903+ }
851904
852- if ascii == ZERO {
853- if let ascii2 = nextASCII ( ) {
854- if ascii2 >= ZERO && ascii2 <= NINE {
855- throw NSError ( domain: NSCocoaErrorDomain, code: CocoaError . propertyListReadCorrupt. rawValue,
856- userInfo: [ " NSDebugDescription " : " Leading zeros not allowed at character \( input) . " ] )
905+ digitCount = string. count - ( isInteger ? 0 : 1 ) - ( isNegative ? 1 : 0 )
906+ guard ascii == JSONReader . LOWER_EXPONENT || ascii == JSONReader . UPPER_EXPONENT else {
907+ // End of valid number characters
908+ return true
909+ }
910+ digitCount = digitCount! - 1
911+
912+ // Process the exponent
913+ isInteger = false
914+ guard nextASCII ( ) else { return false }
915+ if ascii == JSONReader . MINUS {
916+ positiveExponent = false
917+ guard nextASCII ( ) else { return false }
918+ } else if ascii == JSONReader . PLUS {
919+ positiveExponent = true
920+ guard nextASCII ( ) else { return false }
921+ }
922+ guard JSONReader . allDigits. contains ( ascii) else { return false }
923+ exponent = Int ( ascii - JSONReader. ZERO)
924+ while nextASCII ( ) {
925+ guard JSONReader . allDigits. contains ( ascii) else { return false } // Invalid exponent character
926+ exponent = ( exponent * 10 ) + Int( ascii - JSONReader. ZERO)
927+ if exponent > 324 {
928+ // Exponent is too large to store in a Double
929+ return false
857930 }
858- string. append ( " 0 " )
859- ascii = ascii2
860931 }
861- } else if ascii < ONE || ascii > NINE {
862- throw NSError ( domain: NSCocoaErrorDomain, code: CocoaError . propertyListReadCorrupt. rawValue,
863- userInfo: [ " NSDebugDescription " : " Numbers must start with a 1-9 at character \( input) . " ] )
864- }
865- string. append ( Character ( UnicodeScalar ( ascii) ) )
866- while let ascii = nextASCII ( ) {
867- string. append ( Character ( UnicodeScalar ( ascii) ) )
932+ return true
868933 }
869934
870- if isNegative {
871- if let intValue = Int64 ( string) {
872- return ( NSNumber ( value: intValue) , index)
873- }
874- } else {
875- if let uintValue = UInt64 ( string) {
876- return ( NSNumber ( value: uintValue) , index)
935+ guard try checkJSONNumber ( ) == true else { return nil }
936+ digitCount = digitCount ?? string. count - ( isInteger ? 0 : 1 ) - ( isNegative ? 1 : 0 )
937+
938+ // Try Int64() or UInt64() first
939+ if isInteger {
940+ if isNegative {
941+ if digitCount! <= 19 , let intValue = Int64 ( string) {
942+ return ( NSNumber ( value: intValue) , index)
943+ }
944+ } else {
945+ if digitCount! <= 20 , let uintValue = UInt64 ( string) {
946+ return ( NSNumber ( value: uintValue) , index)
947+ }
877948 }
878949 }
950+
951+ // Decimal holds more digits of precision but a smaller exponent than Double
952+ // so try that if the exponent fits and there are more digits than Double can hold
953+ if digitCount! > 17 && exponent >= - 128 && exponent <= 127 ,
954+ let decimal = Decimal ( string: string) , decimal. isFinite {
955+ return ( NSDecimalNumber ( decimal: decimal) , index)
956+ }
957+ // Fall back to Double() for everything else
879958 if let doubleValue = Double ( string) {
880959 return ( NSNumber ( value: doubleValue) , index)
881960 }
0 commit comments