diff --git a/Sources/Foundation/JSONEncoder.swift b/Sources/Foundation/JSONEncoder.swift index 2174ac2167..37d8d1bcb6 100644 --- a/Sources/Foundation/JSONEncoder.swift +++ b/Sources/Foundation/JSONEncoder.swift @@ -504,8 +504,8 @@ extension _SpecialTreatmentEncoder { return .string(url.absoluteString) case let decimal as Decimal: return .number(decimal.description) - case let object as [String: Encodable]: - return try self.wrapObject(object, for: additionalKey) + case let object as _JSONStringDictionaryEncodableMarker: + return try self.wrapObject(object as! [String: Encodable], for: additionalKey) default: let encoder = self.getEncoder(for: additionalKey) try encodable.encode(to: encoder) diff --git a/Tests/Foundation/Tests/TestJSONEncoder.swift b/Tests/Foundation/Tests/TestJSONEncoder.swift index c7e61a9b6b..60b83a33d0 100644 --- a/Tests/Foundation/Tests/TestJSONEncoder.swift +++ b/Tests/Foundation/Tests/TestJSONEncoder.swift @@ -869,6 +869,39 @@ class TestJSONEncoder : XCTestCase { XCTAssertEqual(JSONEncoder.OutputFormatting.withoutEscapingSlashes.rawValue, 8) } + func test_SR17581_codingEmptyDictionaryWithNonstringKeyDoesRoundtrip() throws { + struct Something: Codable { + struct Key: Codable, Hashable { + var x: String + } + + var dict: [Key: String] + + enum CodingKeys: String, CodingKey { + case dict + } + + init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + self.dict = try container.decode([Key: String].self, forKey: .dict) + } + + func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(dict, forKey: .dict) + } + + init(dict: [Key: String]) { + self.dict = dict + } + } + + let toEncode = Something(dict: [:]) + let data = try JSONEncoder().encode(toEncode) + let result = try JSONDecoder().decode(Something.self, from: data) + XCTAssertEqual(result.dict.count, 0) + } + // MARK: - Helper Functions private var _jsonEmptyDictionary: Data { return "{}".data(using: .utf8)! @@ -1471,6 +1504,7 @@ extension TestJSONEncoder { ("test_dictionary_snake_case_decoding", test_dictionary_snake_case_decoding), ("test_dictionary_snake_case_encoding", test_dictionary_snake_case_encoding), ("test_OutputFormattingValues", test_OutputFormattingValues), + ("test_SR17581_codingEmptyDictionaryWithNonstringKeyDoesRoundtrip", test_SR17581_codingEmptyDictionaryWithNonstringKeyDoesRoundtrip), ] } }