@@ -816,66 +816,145 @@ private struct JSONReader {
816
816
}
817
817
818
818
//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
+
823
836
824
837
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: " - " )
829
838
830
839
var isNegative = false
831
840
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
835
844
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
+ }
842
862
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
+ }
851
904
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
857
930
}
858
- string. append ( " 0 " )
859
- ascii = ascii2
860
931
}
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
868
933
}
869
934
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
+ }
877
948
}
878
949
}
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
879
958
if let doubleValue = Double ( string) {
880
959
return ( NSNumber ( value: doubleValue) , index)
881
960
}
0 commit comments