Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added protocol CodingKeyRepresentable #34458

Merged
merged 36 commits into from
Oct 30, 2021
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
54414b4
Added protocol StringKeyCodable which allows you to annotate types th…
mortenbekditlevsen Oct 27, 2020
3e9e7dd
Updated the code to be in line with the updated pitch
mortenbekditlevsen Jan 24, 2021
65cb363
Update stdlib/public/core/Codable.swift
mortenbekditlevsen Aug 14, 2021
aad2b7d
Update stdlib/public/core/Codable.swift
mortenbekditlevsen Aug 14, 2021
353eb15
Update stdlib/public/core/Codable.swift
mortenbekditlevsen Aug 14, 2021
aef4ddb
Update stdlib/public/core/Codable.swift
mortenbekditlevsen Aug 14, 2021
676ec26
Added debugDescription to thrown error
mortenbekditlevsen Aug 14, 2021
2583985
Merge branch 'main' of https://github.com/apple/swift into stringkeyc…
mortenbekditlevsen Aug 14, 2021
5d496ea
Added tests for CodingKeyRepresentable encoding and decoding
mortenbekditlevsen Aug 17, 2021
1800329
Incorporated feedback from Jordan Rose: Int and String conforms to Co…
mortenbekditlevsen Aug 17, 2021
9442a19
Refactoring test_Dictionary_JSON
mortenbekditlevsen Aug 17, 2021
7f260c9
Refactored and expanded test_Dictionary_JSON
mortenbekditlevsen Aug 17, 2021
766a598
Fix some indentation
mortenbekditlevsen Aug 17, 2021
74e03c0
Update test/stdlib/CodableTests.swift
mortenbekditlevsen Aug 18, 2021
0e75b32
Incorporated feedback for tests
mortenbekditlevsen Aug 18, 2021
e07b552
Merge branch 'stringkeycodable' of https://github.com/mortenbekditlev…
mortenbekditlevsen Aug 18, 2021
a929c33
Updated documentation comment for Dictionary.encode(to:) and CodingKe…
mortenbekditlevsen Aug 18, 2021
4cea780
Added test for non-Int,String RawRepresentable
mortenbekditlevsen Aug 18, 2021
089b0c9
Added tests for dictionaries keyed by UUID and UUIDCodingWrapper
mortenbekditlevsen Aug 18, 2021
8054c78
Added availability checks for all public api
mortenbekditlevsen Sep 16, 2021
ec6c172
Also added availability checks for protocol requirements
mortenbekditlevsen Sep 16, 2021
e9ad151
Added requested change to cake-abi.json
mortenbekditlevsen Sep 17, 2021
97eb27e
Added requested change to cake.json
mortenbekditlevsen Sep 18, 2021
8b50f48
Remove extra newline at end of file
mortenbekditlevsen Sep 19, 2021
2dce12a
Update cake-abi.json
mortenbekditlevsen Sep 19, 2021
ae2430f
Remove newlines at end of cake.json and cake-abi.json
mortenbekditlevsen Sep 19, 2021
03c0adc
Merge branch 'stringkeycodable' of https://github.com/mortenbekditlev…
mortenbekditlevsen Sep 19, 2021
bd015f4
Accept documentation comment change suggestions.
mortenbekditlevsen Sep 29, 2021
79f05cc
Updated documentation comments based on suggestions from review
mortenbekditlevsen Sep 29, 2021
6cb65c5
Documentation change: Added serial comma before 'or'
mortenbekditlevsen Sep 30, 2021
729212b
Respect 80-character line limit.
mortenbekditlevsen Oct 11, 2021
0e27ae6
Respect 80-character line limit.
mortenbekditlevsen Oct 11, 2021
9c2dae5
Respect 80-character line limit.
mortenbekditlevsen Oct 11, 2021
46a311b
Respect 80-character line limit.
mortenbekditlevsen Oct 11, 2021
871fc88
Update test/api-digester/Outputs/cake-abi.json
mortenbekditlevsen Oct 30, 2021
27f7abe
Update test/api-digester/Outputs/cake.json
mortenbekditlevsen Oct 30, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 91 additions & 4 deletions stdlib/public/core/Codable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5510,15 +5510,74 @@ internal struct _DictionaryCodingKey: CodingKey {
internal let stringValue: String
internal let intValue: Int?

internal init?(stringValue: String) {
internal init(stringValue: String) {
self.stringValue = stringValue
self.intValue = Int(stringValue)
}

internal init?(intValue: Int) {
internal init(intValue: Int) {
self.stringValue = "\(intValue)"
self.intValue = intValue
}

fileprivate init(codingKey: CodingKey) {
self.stringValue = codingKey.stringValue
self.intValue = codingKey.intValue
}
}

@available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *)
public protocol CodingKeyRepresentable {
var codingKey: CodingKey { get }
init?<T: CodingKey>(codingKey: T)
}

@available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *)
extension RawRepresentable where Self: CodingKeyRepresentable, RawValue == String {
public var codingKey: CodingKey {
_DictionaryCodingKey(stringValue: rawValue)
}
public init?<T: CodingKey>(codingKey: T) {
self.init(rawValue: codingKey.stringValue)
}
}

@available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *)
extension RawRepresentable where Self: CodingKeyRepresentable, RawValue == Int {
public var codingKey: CodingKey {
_DictionaryCodingKey(intValue: rawValue)
}
public init?<T: CodingKey>(codingKey: T) {
if let intValue = codingKey.intValue {
self.init(rawValue: intValue)
} else {
return nil
}
}
}

@available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *)
extension Int: CodingKeyRepresentable {
public var codingKey: CodingKey {
_DictionaryCodingKey(intValue: self)
}
public init?<T: CodingKey>(codingKey: T) {
if let intValue = codingKey.intValue {
self = intValue
} else {
return nil
}
}
}

@available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *)
extension String: CodingKeyRepresentable {
public var codingKey: CodingKey {
_DictionaryCodingKey(stringValue: self)
}
public init?<T: CodingKey>(codingKey: T) {
self = codingKey.stringValue
}
}

extension Dictionary: Encodable where Key: Encodable, Value: Encodable {
Expand All @@ -5537,16 +5596,25 @@ extension Dictionary: Encodable where Key: Encodable, Value: Encodable {
// Since the keys are already Strings, we can use them as keys directly.
var container = encoder.container(keyedBy: _DictionaryCodingKey.self)
for (key, value) in self {
let codingKey = _DictionaryCodingKey(stringValue: key as! String)!
let codingKey = _DictionaryCodingKey(stringValue: key as! String)
try container.encode(value, forKey: codingKey)
}
} else if Key.self == Int.self {
// Since the keys are already Ints, we can use them as keys directly.
var container = encoder.container(keyedBy: _DictionaryCodingKey.self)
for (key, value) in self {
let codingKey = _DictionaryCodingKey(intValue: key as! Int)!
let codingKey = _DictionaryCodingKey(intValue: key as! Int)
try container.encode(value, forKey: codingKey)
}
} else if #available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *), Key.self is CodingKeyRepresentable.Type {
// Since the keys are CodingKeyRepresentable, we can use the `codingKey`
// to create `_DictionaryCodingKey` instances.
var container = encoder.container(keyedBy: _DictionaryCodingKey.self)
for (key, value) in self {
let codingKey = (key as! CodingKeyRepresentable).codingKey
let dictionaryCodingKey = _DictionaryCodingKey(codingKey: codingKey)
try container.encode(value, forKey: dictionaryCodingKey)
}
} else {
// Keys are Encodable but not Strings or Ints, so we cannot arbitrarily
// convert to keys. We can encode as an array of alternating key-value
Expand Down Expand Up @@ -5601,6 +5669,25 @@ extension Dictionary: Decodable where Key: Decodable, Value: Decodable {
let value = try container.decode(Value.self, forKey: key)
self[key.intValue! as! Key] = value
}
} else if #available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *), let codingKeyRepresentableType = Key.self as? CodingKeyRepresentable.Type {
// The keys are CodingKeyRepresentable, so we should be able to expect a keyed container.
let container = try decoder.container(keyedBy: _DictionaryCodingKey.self)
for dictionaryCodingKey in container.allKeys {
guard let key: Key = codingKeyRepresentableType.init(
codingKey: dictionaryCodingKey
) as? Key else {
throw DecodingError.dataCorruptedError(
forKey: dictionaryCodingKey,
in: container,
debugDescription: "Could not convert key to type \(Key.self)"
)
}
let value: Value = try container.decode(
Value.self,
forKey: dictionaryCodingKey
)
self[key] = value
}
} else {
// We should have encoded as an array of alternating key-value pairs.
var container = try decoder.unkeyedContainer()
Expand Down
66 changes: 64 additions & 2 deletions test/stdlib/CodableTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -76,14 +76,23 @@ func expectRoundTripEquality<T : Codable>(of value: T, encode: (T) throws -> Dat
expectEqual(value, decoded, "\(#file):\(lineNumber): Decoded \(T.self) <\(debugDescription(decoded))> not equal to original <\(debugDescription(value))>")
}

func expectRoundTripEqualityThroughJSON<T : Codable>(for value: T, lineNumber: Int) where T : Equatable {
func expectRoundTripEqualityThroughJSON<T : Codable>(for value: T, _ expectedJSON: String? = nil, lineNumber: Int) where T : Equatable {
let inf = "INF", negInf = "-INF", nan = "NaN"
let encode = { (_ value: T) throws -> Data in
let encoder = JSONEncoder()
encoder.nonConformingFloatEncodingStrategy = .convertToString(positiveInfinity: inf,
negativeInfinity: negInf,
nan: nan)
return try encoder.encode(value)
if #available(macOS 10.13, iOS 11.0, watchOS 4.0, tvOS 11.0, *) {
encoder.outputFormatting = .sortedKeys
}
let encoded = try encoder.encode(value)

if let expectedJSON = expectedJSON {
let stringValue = String(decoding: encoded, as: UTF8.self)
expectEqual(expectedJSON, stringValue, "\(#file):\(lineNumber): Encoded \(T.self) <\(stringValue)> not equal to expected <\(expectedJSON)>")
}
return encoded
}

let decode = { (_ type: T.Type, _ data: Data) throws -> T in
Expand Down Expand Up @@ -462,6 +471,58 @@ class TestCodable : TestCodableSuper {
}
}

func test_Dictionary_JSON() {
enum X: String, Codable { case a, b }
enum Y: String, Codable, CodingKeyRepresentable { case a, b }
enum Z: String, Codable, CodingKeyRepresentable {
case a
case b
init?<T: CodingKey>(codingKey: T) {
self.init(rawValue: codingKey.stringValue)
}
var codingKey: CodingKey {
GenericCodingKey(stringValue: self.rawValue)
}
}

enum U: Int, Codable { case a = 0, b}
enum V: Int, Codable, CodingKeyRepresentable { case a = 0, b }
enum W: Int, Codable, CodingKeyRepresentable {
case a = 0
case b
init?<T: CodingKey>(codingKey: T) {
self.init(rawValue: codingKey.intValue!)
}
var codingKey: CodingKey {
GenericCodingKey(intValue: self.rawValue)
}
}


struct GenericCodingKey: CodingKey {
var stringValue: String
var intValue: Int?

init(stringValue: String) {
self.stringValue = stringValue
}

init(intValue: Int) {
self.stringValue = "\(intValue)"
self.intValue = intValue
}
}

expectRoundTripEqualityThroughJSON(for: [X.a: true], #"["a",true]"#, lineNumber: #line)
expectRoundTripEqualityThroughJSON(for: [Y.a: true, Y.b: false], #"{"a":true,"b":false}"#, lineNumber: #line)
expectRoundTripEqualityThroughJSON(for: [Z.a: true, Z.b: false], #"{"a":true,"b":false}"#, lineNumber: #line)

expectRoundTripEqualityThroughJSON(for: [U.a: true], #"[0,true]"#, lineNumber: #line)
expectRoundTripEqualityThroughJSON(for: [V.a: true, V.b: false], #"{"0":true,"1":false}"#, lineNumber: #line)
expectRoundTripEqualityThroughJSON(for: [W.a: true, W.b: false], #"{"0":true,"1":false}"#, lineNumber: #line)
}


// MARK: - IndexPath
lazy var indexPathValues: [Int : IndexPath] = [
#line : IndexPath(), // empty
Expand Down Expand Up @@ -891,6 +952,7 @@ var tests = [
"test_DateComponents_Plist" : TestCodable.test_DateComponents_Plist,
"test_Decimal_JSON" : TestCodable.test_Decimal_JSON,
"test_Decimal_Plist" : TestCodable.test_Decimal_Plist,
"test_Dictionary_JSON": TestCodable.test_Dictionary_JSON,
"test_IndexPath_JSON" : TestCodable.test_IndexPath_JSON,
"test_IndexPath_Plist" : TestCodable.test_IndexPath_Plist,
"test_IndexSet_JSON" : TestCodable.test_IndexSet_JSON,
Expand Down