@@ -19,17 +19,44 @@ import Darwin
1919internal import _FoundationCShims
2020internal import Synchronization
2121
22- /// A marker protocol used to determine whether a value is a `String `-keyed `Dictionary`
22+ /// A marker protocol used to determine whether a value is a `CodingKeyRepresentable `-keyed `Dictionary`
2323/// containing `Decodable` values (in which case it should be exempt from key conversion strategies).
2424///
25- /// The marker protocol also provides access to the type of the `Decodable` values,
26- /// which is needed for the implementation of the key conversion strategy exemption .
27- private protocol _JSONStringDictionaryDecodableMarker {
25+ /// The protocol provides `_fromStringKeyedDictionary` to convert a `[String: Any]` dictionary
26+ /// to the proper `[Key: Value]` type using `Key.init(codingKey:)` .
27+ private protocol _JSONCodingKeyRepresentableDictionaryDecodableMarker {
2828 static var elementType : Decodable . Type { get }
29+ static func _fromStringKeyedDictionary( _ dict: [ String : Any ] ) -> Self ?
2930}
3031
31- extension Dictionary : _JSONStringDictionaryDecodableMarker where Key == String , Value: Decodable {
32+ extension Dictionary : _JSONCodingKeyRepresentableDictionaryDecodableMarker where Key: CodingKeyRepresentable , Value: Decodable {
3233 static var elementType : Decodable . Type { return Value . self }
34+
35+ static func _fromStringKeyedDictionary( _ dict: [ String : Any ] ) -> Self ? {
36+ // Fast path for String keys - no conversion needed
37+ if Key . self == String . self {
38+ return dict as? Self
39+ }
40+ // Convert keys for other CodingKeyRepresentable types
41+ var result = Self ( )
42+ result. reserveCapacity ( dict. count)
43+ for (stringKey, value) in dict {
44+ guard let key = Key ( codingKey: _DictionaryCodingKey ( stringValue: stringKey) ) ,
45+ let typedValue = value as? Value else {
46+ return nil
47+ }
48+ result [ key] = typedValue
49+ }
50+ return result
51+ }
52+ }
53+
54+ /// A simple CodingKey implementation for string-to-key conversion.
55+ private struct _DictionaryCodingKey : CodingKey {
56+ var stringValue : String
57+ var intValue : Int ? { nil }
58+ init ( stringValue: String ) { self . stringValue = stringValue }
59+ init ? ( intValue: Int ) { nil }
3360}
3461
3562//===----------------------------------------------------------------------===//
@@ -611,7 +638,7 @@ extension JSONDecoderImpl: Decoder {
611638 if type == Decimal . self {
612639 return try self . unwrapDecimal ( from: mapValue, for: codingPathNode, additionalKey) as! T
613640 }
614- if !options. keyDecodingStrategy. isDefault, T . self is _JSONStringDictionaryDecodableMarker . Type {
641+ if !options. keyDecodingStrategy. isDefault, T . self is _JSONCodingKeyRepresentableDictionaryDecodableMarker . Type {
615642 return try self . unwrapDictionary ( from: mapValue, as: type, for: codingPathNode, additionalKey)
616643 }
617644
@@ -770,8 +797,8 @@ extension JSONDecoderImpl: Decoder {
770797 private func unwrapDictionary< T: Decodable > ( from mapValue: JSONMap . Value , as type: T . Type , for codingPathNode: _CodingPathNode , _ additionalKey: ( some CodingKey ) ? = nil ) throws -> T {
771798 try checkNotNull ( mapValue, expectedType: [ String : Any ] . self, for: codingPathNode, additionalKey)
772799
773- guard let dictType = type as? ( _JSONStringDictionaryDecodableMarker & Decodable ) . Type else {
774- preconditionFailure ( " Must only be called if T implements __JSONStringDictionaryDecodableMarker " )
800+ guard let dictType = type as? _JSONCodingKeyRepresentableDictionaryDecodableMarker . Type else {
801+ preconditionFailure ( " Must only be called if T implements _JSONCodingKeyRepresentableDictionaryDecodableMarker " )
775802 }
776803
777804 guard case let . object( region) = mapValue else {
@@ -781,8 +808,8 @@ extension JSONDecoderImpl: Decoder {
781808 ) )
782809 }
783810
784- var result = [ String: Any] ( )
785- result . reserveCapacity ( region. count / 2 )
811+ var stringKeyedResult = [ String: Any] ( )
812+ stringKeyedResult . reserveCapacity ( region. count / 2 )
786813
787814 let dictCodingPathNode = codingPathNode. appending ( additionalKey)
788815
@@ -791,10 +818,17 @@ extension JSONDecoderImpl: Decoder {
791818 // We know these values are keys, but UTF-8 decoding could still fail.
792819 let key = try self . unwrapString ( from: keyValue, for: dictCodingPathNode, _CodingKey? . none)
793820 let value = try self . unwrap ( value, as: dictType. elementType, for: dictCodingPathNode, _CodingKey ( stringValue: key) !)
794- result [ key] . _setIfNil ( to: value)
821+ stringKeyedResult [ key] . _setIfNil ( to: value)
795822 }
796823
797- return result as! T
824+ // Convert [String: Any] to [Key: Value] using the marker protocol
825+ guard let result = dictType. _fromStringKeyedDictionary ( stringKeyedResult) as? T else {
826+ throw DecodingError . dataCorrupted ( DecodingError . Context (
827+ codingPath: codingPathNode. path ( byAppending: additionalKey) ,
828+ debugDescription: " Failed to create dictionary with CodingKeyRepresentable keys "
829+ ) )
830+ }
831+ return result
798832 }
799833
800834 private func unwrapString( from value: JSONMap . Value , for codingPathNode: _CodingPathNode , _ additionalKey: ( some CodingKey ) ? = nil ) throws -> String {
0 commit comments