Skip to content

Commit 0c9e76b

Browse files
authored
Merge pull request #1882 from spevans/pr_sr_9828_50
[5.0] SR-9828: JSONEncoder keyEncodingStrategy does not work on Linux
2 parents 0614537 + ea26054 commit 0c9e76b

File tree

2 files changed

+110
-23
lines changed

2 files changed

+110
-23
lines changed

Foundation/JSONEncoder.swift

Lines changed: 37 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -383,30 +383,44 @@ fileprivate struct _JSONKeyedEncodingContainer<K : CodingKey> : KeyedEncodingCon
383383
self.container = container
384384
}
385385

386+
// MARK: - Coding Path Operations
387+
388+
private func _converted(_ key: CodingKey) -> CodingKey {
389+
switch encoder.options.keyEncodingStrategy {
390+
case .useDefaultKeys:
391+
return key
392+
case .convertToSnakeCase:
393+
let newKeyString = JSONEncoder.KeyEncodingStrategy._convertToSnakeCase(key.stringValue)
394+
return _JSONKey(stringValue: newKeyString, intValue: key.intValue)
395+
case .custom(let converter):
396+
return converter(codingPath + [key])
397+
}
398+
}
399+
386400
// MARK: - KeyedEncodingContainerProtocol Methods
387401

388-
public mutating func encodeNil(forKey key: Key) throws { self.container[key.stringValue._bridgeToObjectiveC()] = NSNull() }
389-
public mutating func encode(_ value: Bool, forKey key: Key) throws { self.container[key.stringValue._bridgeToObjectiveC()] = self.encoder.box(value) }
390-
public mutating func encode(_ value: Int, forKey key: Key) throws { self.container[key.stringValue._bridgeToObjectiveC()] = self.encoder.box(value) }
391-
public mutating func encode(_ value: Int8, forKey key: Key) throws { self.container[key.stringValue._bridgeToObjectiveC()] = self.encoder.box(value) }
392-
public mutating func encode(_ value: Int16, forKey key: Key) throws { self.container[key.stringValue._bridgeToObjectiveC()] = self.encoder.box(value) }
393-
public mutating func encode(_ value: Int32, forKey key: Key) throws { self.container[key.stringValue._bridgeToObjectiveC()] = self.encoder.box(value) }
394-
public mutating func encode(_ value: Int64, forKey key: Key) throws { self.container[key.stringValue._bridgeToObjectiveC()] = self.encoder.box(value) }
395-
public mutating func encode(_ value: UInt, forKey key: Key) throws { self.container[key.stringValue._bridgeToObjectiveC()] = self.encoder.box(value) }
396-
public mutating func encode(_ value: UInt8, forKey key: Key) throws { self.container[key.stringValue._bridgeToObjectiveC()] = self.encoder.box(value) }
397-
public mutating func encode(_ value: UInt16, forKey key: Key) throws { self.container[key.stringValue._bridgeToObjectiveC()] = self.encoder.box(value) }
398-
public mutating func encode(_ value: UInt32, forKey key: Key) throws { self.container[key.stringValue._bridgeToObjectiveC()] = self.encoder.box(value) }
399-
public mutating func encode(_ value: UInt64, forKey key: Key) throws { self.container[key.stringValue._bridgeToObjectiveC()] = self.encoder.box(value) }
400-
public mutating func encode(_ value: String, forKey key: Key) throws { self.container[key.stringValue._bridgeToObjectiveC()] = self.encoder.box(value) }
402+
public mutating func encodeNil(forKey key: Key) throws { self.container[_converted(key).stringValue._bridgeToObjectiveC()] = NSNull() }
403+
public mutating func encode(_ value: Bool, forKey key: Key) throws { self.container[_converted(key).stringValue._bridgeToObjectiveC()] = self.encoder.box(value) }
404+
public mutating func encode(_ value: Int, forKey key: Key) throws { self.container[_converted(key).stringValue._bridgeToObjectiveC()] = self.encoder.box(value) }
405+
public mutating func encode(_ value: Int8, forKey key: Key) throws { self.container[_converted(key).stringValue._bridgeToObjectiveC()] = self.encoder.box(value) }
406+
public mutating func encode(_ value: Int16, forKey key: Key) throws { self.container[_converted(key).stringValue._bridgeToObjectiveC()] = self.encoder.box(value) }
407+
public mutating func encode(_ value: Int32, forKey key: Key) throws { self.container[_converted(key).stringValue._bridgeToObjectiveC()] = self.encoder.box(value) }
408+
public mutating func encode(_ value: Int64, forKey key: Key) throws { self.container[_converted(key).stringValue._bridgeToObjectiveC()] = self.encoder.box(value) }
409+
public mutating func encode(_ value: UInt, forKey key: Key) throws { self.container[_converted(key).stringValue._bridgeToObjectiveC()] = self.encoder.box(value) }
410+
public mutating func encode(_ value: UInt8, forKey key: Key) throws { self.container[_converted(key).stringValue._bridgeToObjectiveC()] = self.encoder.box(value) }
411+
public mutating func encode(_ value: UInt16, forKey key: Key) throws { self.container[_converted(key).stringValue._bridgeToObjectiveC()] = self.encoder.box(value) }
412+
public mutating func encode(_ value: UInt32, forKey key: Key) throws { self.container[_converted(key).stringValue._bridgeToObjectiveC()] = self.encoder.box(value) }
413+
public mutating func encode(_ value: UInt64, forKey key: Key) throws { self.container[_converted(key).stringValue._bridgeToObjectiveC()] = self.encoder.box(value) }
414+
public mutating func encode(_ value: String, forKey key: Key) throws { self.container[_converted(key).stringValue._bridgeToObjectiveC()] = self.encoder.box(value) }
401415

402416
public mutating func encode(_ value: Float, forKey key: Key) throws {
403417
// Since the float may be invalid and throw, the coding path needs to contain this key.
404418
self.encoder.codingPath.append(key)
405419
defer { self.encoder.codingPath.removeLast() }
406420
#if DEPLOYMENT_RUNTIME_SWIFT
407-
self.container[key.stringValue._bridgeToObjectiveC()] = try self.encoder.box(value)
421+
self.container[_converted(key).stringValue._bridgeToObjectiveC()] = try self.encoder.box(value)
408422
#else
409-
self.container[key.stringValue] = try self.encoder.box(value)
423+
self.container[_converted(key).stringValue] = try self.encoder.box(value)
410424
#endif
411425
}
412426

@@ -415,28 +429,28 @@ fileprivate struct _JSONKeyedEncodingContainer<K : CodingKey> : KeyedEncodingCon
415429
self.encoder.codingPath.append(key)
416430
defer { self.encoder.codingPath.removeLast() }
417431
#if DEPLOYMENT_RUNTIME_SWIFT
418-
self.container[key.stringValue._bridgeToObjectiveC()] = try self.encoder.box(value)
432+
self.container[_converted(key).stringValue._bridgeToObjectiveC()] = try self.encoder.box(value)
419433
#else
420-
self.container[key.stringValue] = try self.encoder.box(value)
434+
self.container[_converted(key).stringValue] = try self.encoder.box(value)
421435
#endif
422436
}
423437

424438
public mutating func encode<T : Encodable>(_ value: T, forKey key: Key) throws {
425439
self.encoder.codingPath.append(key)
426440
defer { self.encoder.codingPath.removeLast() }
427441
#if DEPLOYMENT_RUNTIME_SWIFT
428-
self.container[key.stringValue._bridgeToObjectiveC()] = try self.encoder.box(value)
442+
self.container[_converted(key).stringValue._bridgeToObjectiveC()] = try self.encoder.box(value)
429443
#else
430-
self.container[key.stringValue] = try self.encoder.box(value)
444+
self.container[_converted(key).stringValue] = try self.encoder.box(value)
431445
#endif
432446
}
433447

434448
public mutating func nestedContainer<NestedKey>(keyedBy keyType: NestedKey.Type, forKey key: Key) -> KeyedEncodingContainer<NestedKey> {
435449
let dictionary = NSMutableDictionary()
436450
#if DEPLOYMENT_RUNTIME_SWIFT
437-
self.container[key.stringValue._bridgeToObjectiveC()] = dictionary
451+
self.container[_converted(key).stringValue._bridgeToObjectiveC()] = dictionary
438452
#else
439-
self.container[key.stringValue] = dictionary
453+
self.container[_converted(key).stringValue] = dictionary
440454
#endif
441455

442456
self.codingPath.append(key)
@@ -449,9 +463,9 @@ fileprivate struct _JSONKeyedEncodingContainer<K : CodingKey> : KeyedEncodingCon
449463
public mutating func nestedUnkeyedContainer(forKey key: Key) -> UnkeyedEncodingContainer {
450464
let array = NSMutableArray()
451465
#if DEPLOYMENT_RUNTIME_SWIFT
452-
self.container[key.stringValue._bridgeToObjectiveC()] = array
466+
self.container[_converted(key).stringValue._bridgeToObjectiveC()] = array
453467
#else
454-
self.container[key.stringValue] = array
468+
self.container[_converted(key).stringValue] = array
455469
#endif
456470

457471
self.codingPath.append(key)

TestFoundation/TestJSONEncoder.swift

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -605,6 +605,78 @@ class TestJSONEncoder : XCTestCase {
605605
testErrorThrown("Double", "2.7976931348623158e+308", errorMessage: "The given data was not valid JSON.")
606606
}
607607

608+
func test_snake_case_encoding() throws {
609+
struct MyTestData: Codable, Equatable {
610+
let thisIsAString: String
611+
let thisIsABool: Bool
612+
let thisIsAnInt: Int
613+
let thisIsAnInt8: Int8
614+
let thisIsAnInt16: Int16
615+
let thisIsAnInt32: Int32
616+
let thisIsAnInt64: Int64
617+
let thisIsAUint: UInt
618+
let thisIsAUint8: UInt8
619+
let thisIsAUint16: UInt16
620+
let thisIsAUint32: UInt32
621+
let thisIsAUint64: UInt64
622+
let thisIsAFloat: Float
623+
let thisIsADouble: Double
624+
let thisIsADate: Date
625+
let thisIsAnArray: Array<Int>
626+
let thisIsADictionary: Dictionary<String, Bool>
627+
}
628+
629+
let data = MyTestData(thisIsAString: "Hello",
630+
thisIsABool: true,
631+
thisIsAnInt: 1,
632+
thisIsAnInt8: 2,
633+
thisIsAnInt16: 3,
634+
thisIsAnInt32: 4,
635+
thisIsAnInt64: 5,
636+
thisIsAUint: 6,
637+
thisIsAUint8: 7,
638+
thisIsAUint16: 8,
639+
thisIsAUint32: 9,
640+
thisIsAUint64: 10,
641+
thisIsAFloat: 11,
642+
thisIsADouble: 12,
643+
thisIsADate: Date.init(timeIntervalSince1970: 0),
644+
thisIsAnArray: [1, 2, 3],
645+
thisIsADictionary: [ "trueValue": true, "falseValue": false]
646+
)
647+
648+
let encoder = JSONEncoder()
649+
encoder.keyEncodingStrategy = .convertToSnakeCase
650+
encoder.dateEncodingStrategy = .iso8601
651+
let encodedData = try encoder.encode(data)
652+
guard let jsonObject = try JSONSerialization.jsonObject(with: encodedData) as? [String: Any] else {
653+
XCTFail("Cant decode json object")
654+
return
655+
}
656+
XCTAssertEqual(jsonObject["this_is_a_string"] as? String, "Hello")
657+
XCTAssertEqual(jsonObject["this_is_a_bool"] as? Bool, true)
658+
XCTAssertEqual(jsonObject["this_is_an_int"] as? Int, 1)
659+
XCTAssertEqual(jsonObject["this_is_an_int8"] as? Int8, 2)
660+
XCTAssertEqual(jsonObject["this_is_an_int16"] as? Int16, 3)
661+
XCTAssertEqual(jsonObject["this_is_an_int32"] as? Int32, 4)
662+
XCTAssertEqual(jsonObject["this_is_an_int64"] as? Int64, 5)
663+
XCTAssertEqual(jsonObject["this_is_a_uint"] as? UInt, 6)
664+
XCTAssertEqual(jsonObject["this_is_a_uint8"] as? UInt8, 7)
665+
XCTAssertEqual(jsonObject["this_is_a_uint16"] as? UInt16, 8)
666+
XCTAssertEqual(jsonObject["this_is_a_uint32"] as? UInt32, 9)
667+
XCTAssertEqual(jsonObject["this_is_a_uint64"] as? UInt64, 10)
668+
XCTAssertEqual(jsonObject["this_is_a_float"] as? Float, 11)
669+
XCTAssertEqual(jsonObject["this_is_a_double"] as? Double, 12)
670+
XCTAssertEqual(jsonObject["this_is_a_date"] as? String, "1970-01-01T00:00:00Z")
671+
XCTAssertEqual(jsonObject["this_is_an_array"] as? [Int], [1, 2, 3])
672+
XCTAssertEqual(jsonObject["this_is_a_dictionary"] as? [String: Bool], ["true_value": true, "false_value": false ])
673+
674+
let decoder = JSONDecoder()
675+
decoder.keyDecodingStrategy = .convertFromSnakeCase
676+
decoder.dateDecodingStrategy = .iso8601
677+
let decodedData = try decoder.decode(MyTestData.self, from: encodedData)
678+
XCTAssertEqual(data, decodedData)
679+
}
608680

609681
// MARK: - Helper Functions
610682
private var _jsonEmptyDictionary: Data {
@@ -1197,6 +1269,7 @@ extension TestJSONEncoder {
11971269
("test_codingOfString", test_codingOfString),
11981270
("test_codingOfURL", test_codingOfURL),
11991271
("test_numericLimits", test_numericLimits),
1272+
("test_snake_case_encoding", test_snake_case_encoding),
12001273
]
12011274
}
12021275
}

0 commit comments

Comments
 (0)