10
10
//
11
11
//===----------------------------------------------------------------------===//
12
12
13
+ /// A marker protocol used to determine whether a value is a `String`-keyed `Dictionary`
14
+ /// containing `Encodable` values (in which case it should be exempt from key conversion strategies).
15
+ ///
16
+ /// NOTE: The architecture and environment check is due to a bug in the current (2018-08-08) Swift 4.2
17
+ /// runtime when running on i386 simulator. The issue is tracked in https://bugs.swift.org/browse/SR-8276
18
+ /// Making the protocol `internal` instead of `fileprivate` works around this issue.
19
+ /// Once SR-8276 is fixed, this check can be removed and the protocol always be made fileprivate.
20
+ #if arch(i386) && targetEnvironment(simulator)
21
+ internal protocol _JSONStringDictionaryEncodableMarker { }
22
+ #else
23
+ fileprivate protocol _JSONStringDictionaryEncodableMarker { }
24
+ #endif
25
+
26
+ extension Dictionary : _JSONStringDictionaryEncodableMarker where Key == String , Value: Encodable { }
27
+
28
+ /// A marker protocol used to determine whether a value is a `String`-keyed `Dictionary`
29
+ /// containing `Decodable` values (in which case it should be exempt from key conversion strategies).
30
+ ///
31
+ /// The marker protocol also provides access to the type of the `Decodable` values,
32
+ /// which is needed for the implementation of the key conversion strategy exemption.
33
+ ///
34
+ /// NOTE: Please see comment above regarding SR-8276
35
+ #if arch(i386) && targetEnvironment(simulator)
36
+ internal protocol _JSONStringDictionaryDecodableMarker {
37
+ static var elementType : Decodable . Type { get }
38
+ }
39
+ #else
40
+ fileprivate protocol _JSONStringDictionaryDecodableMarker {
41
+ static var elementType : Decodable . Type { get }
42
+ }
43
+ #endif
44
+
45
+ extension Dictionary : _JSONStringDictionaryDecodableMarker where Key == String , Value: Decodable {
46
+ static var elementType : Decodable . Type { return Value . self }
47
+ }
48
+
13
49
//===----------------------------------------------------------------------===//
14
50
// JSON Encoder
15
51
//===----------------------------------------------------------------------===//
@@ -815,24 +851,55 @@ extension _JSONEncoder {
815
851
}
816
852
}
817
853
818
- fileprivate func box< T : Encodable > ( _ value: T ) throws -> NSObject {
854
+ fileprivate func box( _ dict: [ String : Encodable ] ) throws -> NSObject ? {
855
+ let depth = self . storage. count
856
+ let result = self . storage. pushKeyedContainer ( )
857
+ do {
858
+ for (key, value) in dict {
859
+ self . codingPath. append ( _JSONKey ( stringValue: key, intValue: nil ) )
860
+ defer { self . codingPath. removeLast ( ) }
861
+ result [ key] = try box ( value)
862
+ }
863
+ } catch {
864
+ // If the value pushed a container before throwing, pop it back off to restore state.
865
+ if self . storage. count > depth {
866
+ let _ = self . storage. popContainer ( )
867
+ }
868
+
869
+ throw error
870
+ }
871
+
872
+ // The top container should be a new container.
873
+ guard self . storage. count > depth else {
874
+ return nil
875
+ }
876
+
877
+ return self . storage. popContainer ( )
878
+ }
879
+
880
+ fileprivate func box( _ value: Encodable ) throws -> NSObject {
819
881
return try self . box_ ( value) ?? NSDictionary ( )
820
882
}
821
883
822
884
// This method is called "box_" instead of "box" to disambiguate it from the overloads. Because the return type here is different from all of the "box" overloads (and is more general), any "box" calls in here would call back into "box" recursively instead of calling the appropriate overload, which is not what we want.
823
- fileprivate func box_< T : Encodable > ( _ value: T ) throws -> NSObject ? {
824
- if T . self == Date . self || T . self == NSDate . self {
885
+ fileprivate func box_( _ value: Encodable ) throws -> NSObject ? {
886
+ // Disambiguation between variable and function is required due to
887
+ // issue tracked at: https://bugs.swift.org/browse/SR-1846
888
+ let type = Swift . type ( of: value)
889
+ if type == Date . self || type == NSDate . self {
825
890
// Respect Date encoding strategy
826
891
return try self . box ( ( value as! Date ) )
827
- } else if T . self == Data . self || T . self == NSData . self {
892
+ } else if type == Data . self || type == NSData . self {
828
893
// Respect Data encoding strategy
829
894
return try self . box ( ( value as! Data ) )
830
- } else if T . self == URL . self || T . self == NSURL . self {
895
+ } else if type == URL . self || type == NSURL . self {
831
896
// Encode URLs as single strings.
832
897
return self . box ( ( value as! URL ) . absoluteString)
833
- } else if T . self == Decimal . self || T . self == NSDecimalNumber . self {
898
+ } else if type == Decimal . self || type == NSDecimalNumber . self {
834
899
// JSONSerialization can natively handle NSDecimalNumber.
835
900
return ( value as! NSDecimalNumber )
901
+ } else if value is _JSONStringDictionaryEncodableMarker {
902
+ return try self . box ( value as! [ String : Encodable ] )
836
903
}
837
904
838
905
// The value should request a container from the _JSONEncoder.
@@ -2359,11 +2426,34 @@ extension _JSONDecoder {
2359
2426
}
2360
2427
}
2361
2428
2429
+ fileprivate func unbox< T> ( _ value: Any , as type: _JSONStringDictionaryDecodableMarker . Type ) throws -> T ? {
2430
+ guard !( value is NSNull ) else { return nil }
2431
+
2432
+ var result = [ String : Any] ( )
2433
+ guard let dict = value as? NSDictionary else {
2434
+ throw DecodingError . _typeMismatch ( at: self . codingPath, expectation: type, reality: value)
2435
+ }
2436
+ let elementType = type. elementType
2437
+ for (key, value) in dict {
2438
+ let key = key as! String
2439
+ self . codingPath. append ( _JSONKey ( stringValue: key, intValue: nil ) )
2440
+ defer { self . codingPath. removeLast ( ) }
2441
+
2442
+ result [ key] = try unbox_ ( value, as: elementType)
2443
+ }
2444
+
2445
+ return result as? T
2446
+ }
2447
+
2362
2448
fileprivate func unbox< T : Decodable > ( _ value: Any , as type: T . Type ) throws -> T ? {
2449
+ return try unbox_ ( value, as: type) as? T
2450
+ }
2451
+
2452
+ fileprivate func unbox_( _ value: Any , as type: Decodable . Type ) throws -> Any ? {
2363
2453
if type == Date . self || type == NSDate . self {
2364
- return try self . unbox ( value, as: Date . self) as? T
2454
+ return try self . unbox ( value, as: Date . self)
2365
2455
} else if type == Data . self || type == NSData . self {
2366
- return try self . unbox ( value, as: Data . self) as? T
2456
+ return try self . unbox ( value, as: Data . self)
2367
2457
} else if type == URL . self || type == NSURL . self {
2368
2458
guard let urlString = try self . unbox ( value, as: String . self) else {
2369
2459
return nil
@@ -2373,10 +2463,11 @@ extension _JSONDecoder {
2373
2463
throw DecodingError . dataCorrupted ( DecodingError . Context ( codingPath: self . codingPath,
2374
2464
debugDescription: " Invalid URL string. " ) )
2375
2465
}
2376
-
2377
- return ( url as! T )
2466
+ return url
2378
2467
} else if type == Decimal . self || type == NSDecimalNumber . self {
2379
- return try self . unbox ( value, as: Decimal . self) as? T
2468
+ return try self . unbox ( value, as: Decimal . self)
2469
+ } else if let stringKeyedDictType = type as? _JSONStringDictionaryDecodableMarker . Type {
2470
+ return try self . unbox ( value, as: stringKeyedDictType)
2380
2471
} else {
2381
2472
self . storage. push ( container: value)
2382
2473
defer { self . storage. popContainer ( ) }
0 commit comments