Files
swift-mirror/test/stdlib/TestJSONEncoder.swift
Itai Ferber 3a9d888972 Fix JSON+plist codingPaths for nested values
codingPath more often than not actually needs to be copied, not just
referenced. This makes a big difference for nested containers and
subobjects, which were getting the wrong codingPath values when asking
for them.

This also adds unit tests for JSONEncoder and PropertyListEncoder to
confirm expected behavior.
2017-05-11 17:02:46 -07:00

694 lines
28 KiB
Swift

// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
//
// RUN: %target-run-simple-swift
// REQUIRES: executable_test
// REQUIRES: objc_interop
import Swift
import Foundation
// MARK: - Test Suite
#if FOUNDATION_XCTEST
import XCTest
class TestJSONEncoderSuper : XCTestCase { }
#else
import StdlibUnittest
class TestJSONEncoderSuper { }
#endif
class TestJSONEncoder : TestJSONEncoderSuper {
// MARK: - Encoding Top-Level Empty Types
func testEncodingTopLevelEmptyStruct() {
let empty = EmptyStruct()
_testRoundTrip(of: empty, expectedJSON: _jsonEmptyDictionary)
}
func testEncodingTopLevelEmptyClass() {
let empty = EmptyClass()
_testRoundTrip(of: empty, expectedJSON: _jsonEmptyDictionary)
}
// MARK: - Encoding Top-Level Single-Value Types
func testEncodingTopLevelSingleValueEnum() {
_testEncodeFailure(of: Switch.off)
_testEncodeFailure(of: Switch.on)
}
func testEncodingTopLevelSingleValueStruct() {
_testEncodeFailure(of: Timestamp(3141592653))
}
func testEncodingTopLevelSingleValueClass() {
_testEncodeFailure(of: Counter())
}
// MARK: - Encoding Top-Level Structured Types
func testEncodingTopLevelStructuredStruct() {
// Address is a struct type with multiple fields.
let address = Address.testValue
_testRoundTrip(of: address)
}
func testEncodingTopLevelStructuredClass() {
// Person is a class with multiple fields.
let person = Person.testValue
_testRoundTrip(of: person)
}
func testEncodingTopLevelDeepStructuredType() {
// Company is a type with fields which are Codable themselves.
let company = Company.testValue
_testRoundTrip(of: company)
}
// MARK: - Date Strategy Tests
func testEncodingDate() {
// We can't encode a top-level Date, so it'll be wrapped in an array.
_testRoundTrip(of: TopLevelWrapper(Date()))
}
func testEncodingDateSecondsSince1970() {
// Cannot encode an arbitrary number of seconds since we've lost precision since 1970.
let seconds = 1000.0
let expectedJSON = "[1000]".data(using: .utf8)!
// We can't encode a top-level Date, so it'll be wrapped in an array.
_testRoundTrip(of: TopLevelWrapper(Date(timeIntervalSince1970: seconds)),
expectedJSON: expectedJSON,
dateEncodingStrategy: .secondsSince1970,
dateDecodingStrategy: .secondsSince1970)
}
func testEncodingDateMillisecondsSince1970() {
// Cannot encode an arbitrary number of seconds since we've lost precision since 1970.
let seconds = 1000.0
let expectedJSON = "[1000000]".data(using: .utf8)!
// We can't encode a top-level Date, so it'll be wrapped in an array.
_testRoundTrip(of: TopLevelWrapper(Date(timeIntervalSince1970: seconds)),
expectedJSON: expectedJSON,
dateEncodingStrategy: .millisecondsSince1970,
dateDecodingStrategy: .millisecondsSince1970)
}
func testEncodingDateISO8601() {
if #available(OSX 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *) {
let formatter = ISO8601DateFormatter()
formatter.formatOptions = .withInternetDateTime
let timestamp = Date(timeIntervalSince1970: 1000)
let expectedJSON = "[\"\(formatter.string(from: timestamp))\"]".data(using: .utf8)!
// We can't encode a top-level Date, so it'll be wrapped in an array.
_testRoundTrip(of: TopLevelWrapper(timestamp),
expectedJSON: expectedJSON,
dateEncodingStrategy: .iso8601,
dateDecodingStrategy: .iso8601)
}
}
func testEncodingDateFormatted() {
let formatter = DateFormatter()
formatter.dateStyle = .full
formatter.timeStyle = .full
let timestamp = Date(timeIntervalSince1970: 1000)
let expectedJSON = "[\"\(formatter.string(from: timestamp))\"]".data(using: .utf8)!
// We can't encode a top-level Date, so it'll be wrapped in an array.
_testRoundTrip(of: TopLevelWrapper(timestamp),
expectedJSON: expectedJSON,
dateEncodingStrategy: .formatted(formatter),
dateDecodingStrategy: .formatted(formatter))
}
func testEncodingDateCustom() {
let timestamp = Date()
// We'll encode a number instead of a date.
let encode = { (_ data: Date, _ encoder: Encoder) throws -> Void in
var container = encoder.singleValueContainer()
try container.encode(42)
}
let decode = { (_: Decoder) throws -> Date in return timestamp }
// We can't encode a top-level Date, so it'll be wrapped in an array.
let expectedJSON = "[42]".data(using: .utf8)!
_testRoundTrip(of: TopLevelWrapper(timestamp),
expectedJSON: expectedJSON,
dateEncodingStrategy: .custom(encode),
dateDecodingStrategy: .custom(decode))
}
func testEncodingDateCustomEmpty() {
let timestamp = Date()
// Encoding nothing should encode an empty keyed container ({}).
let encode = { (_: Date, _: Encoder) throws -> Void in }
let decode = { (_: Decoder) throws -> Date in return timestamp }
// We can't encode a top-level Date, so it'll be wrapped in an array.
let expectedJSON = "[{}]".data(using: .utf8)!
_testRoundTrip(of: TopLevelWrapper(timestamp),
expectedJSON: expectedJSON,
dateEncodingStrategy: .custom(encode),
dateDecodingStrategy: .custom(decode))
}
// MARK: - Data Strategy Tests
func testEncodingBase64Data() {
let data = Data(bytes: [0xDE, 0xAD, 0xBE, 0xEF])
// We can't encode a top-level Data, so it'll be wrapped in an array.
let expectedJSON = "[\"3q2+7w==\"]".data(using: .utf8)!
_testRoundTrip(of: TopLevelWrapper(data), expectedJSON: expectedJSON)
}
func testEncodingCustomData() {
// We'll encode a number instead of data.
let encode = { (_ data: Data, _ encoder: Encoder) throws -> Void in
var container = encoder.singleValueContainer()
try container.encode(42)
}
let decode = { (_: Decoder) throws -> Data in return Data() }
// We can't encode a top-level Data, so it'll be wrapped in an array.
let expectedJSON = "[42]".data(using: .utf8)!
_testRoundTrip(of: TopLevelWrapper(Data()),
expectedJSON: expectedJSON,
dataEncodingStrategy: .custom(encode),
dataDecodingStrategy: .custom(decode))
}
func testEncodingCustomDataEmpty() {
// Encoding nothing should encode an empty keyed container ({}).
let encode = { (_: Data, _: Encoder) throws -> Void in }
let decode = { (_: Decoder) throws -> Data in return Data() }
// We can't encode a top-level Data, so it'll be wrapped in an array.
let expectedJSON = "[{}]".data(using: .utf8)!
_testRoundTrip(of: TopLevelWrapper(Data()),
expectedJSON: expectedJSON,
dataEncodingStrategy: .custom(encode),
dataDecodingStrategy: .custom(decode))
}
// MARK: - Non-Conforming Floating Point Strategy Tests
func testEncodingNonConformingFloats() {
_testEncodeFailure(of: TopLevelWrapper(Float.infinity))
_testEncodeFailure(of: TopLevelWrapper(-Float.infinity))
_testEncodeFailure(of: TopLevelWrapper(Float.nan))
_testEncodeFailure(of: TopLevelWrapper(Double.infinity))
_testEncodeFailure(of: TopLevelWrapper(-Double.infinity))
_testEncodeFailure(of: TopLevelWrapper(Double.nan))
}
func testEncodingNonConformingFloatStrings() {
let encodingStrategy: JSONEncoder.NonConformingFloatEncodingStrategy = .convertToString(positiveInfinity: "INF", negativeInfinity: "-INF", nan: "NaN")
let decodingStrategy: JSONDecoder.NonConformingFloatDecodingStrategy = .convertFromString(positiveInfinity: "INF", negativeInfinity: "-INF", nan: "NaN")
_testRoundTrip(of: TopLevelWrapper(Float.infinity),
expectedJSON: "[\"INF\"]".data(using: .utf8)!,
nonConformingFloatEncodingStrategy: encodingStrategy,
nonConformingFloatDecodingStrategy: decodingStrategy)
_testRoundTrip(of: TopLevelWrapper(-Float.infinity),
expectedJSON: "[\"-INF\"]".data(using: .utf8)!,
nonConformingFloatEncodingStrategy: encodingStrategy,
nonConformingFloatDecodingStrategy: decodingStrategy)
// Since Float.nan != Float.nan, we have to use a placeholder that'll encode NaN but actually round-trip.
_testRoundTrip(of: TopLevelWrapper(FloatNaNPlaceholder()),
expectedJSON: "[\"NaN\"]".data(using: .utf8)!,
nonConformingFloatEncodingStrategy: encodingStrategy,
nonConformingFloatDecodingStrategy: decodingStrategy)
_testRoundTrip(of: TopLevelWrapper(Double.infinity),
expectedJSON: "[\"INF\"]".data(using: .utf8)!,
nonConformingFloatEncodingStrategy: encodingStrategy,
nonConformingFloatDecodingStrategy: decodingStrategy)
_testRoundTrip(of: TopLevelWrapper(-Double.infinity),
expectedJSON: "[\"-INF\"]".data(using: .utf8)!,
nonConformingFloatEncodingStrategy: encodingStrategy,
nonConformingFloatDecodingStrategy: decodingStrategy)
// Since Double.nan != Double.nan, we have to use a placeholder that'll encode NaN but actually round-trip.
_testRoundTrip(of: TopLevelWrapper(DoubleNaNPlaceholder()),
expectedJSON: "[\"NaN\"]".data(using: .utf8)!,
nonConformingFloatEncodingStrategy: encodingStrategy,
nonConformingFloatDecodingStrategy: decodingStrategy)
}
// MARK: - Encoder Features
func testNestedContainerCodingPaths() {
let encoder = JSONEncoder()
do {
let _ = try encoder.encode(NestedContainersTestType())
} catch let error as NSError {
expectUnreachable("Caught error during encoding nested container types: \(error)")
}
}
func testSuperEncoderCodingPaths() {
let encoder = JSONEncoder()
do {
let _ = try encoder.encode(NestedContainersTestType(testSuperEncoder: true))
} catch let error as NSError {
expectUnreachable("Caught error during encoding nested container types: \(error)")
}
}
// MARK: - Helper Functions
private var _jsonEmptyDictionary: Data {
return "{}".data(using: .utf8)!
}
private func _testEncodeFailure<T : Encodable>(of value: T) {
do {
let _ = try JSONEncoder().encode(value)
expectUnreachable("Encode of top-level \(T.self) was expected to fail.")
} catch {}
}
private func _testRoundTrip<T>(of value: T,
expectedJSON json: Data? = nil,
dateEncodingStrategy: JSONEncoder.DateEncodingStrategy = .deferredToDate,
dateDecodingStrategy: JSONDecoder.DateDecodingStrategy = .deferredToDate,
dataEncodingStrategy: JSONEncoder.DataEncodingStrategy = .base64Encode,
dataDecodingStrategy: JSONDecoder.DataDecodingStrategy = .base64Decode,
nonConformingFloatEncodingStrategy: JSONEncoder.NonConformingFloatEncodingStrategy = .throw,
nonConformingFloatDecodingStrategy: JSONDecoder.NonConformingFloatDecodingStrategy = .throw) where T : Codable, T : Equatable {
var payload: Data! = nil
do {
let encoder = JSONEncoder()
encoder.dateEncodingStrategy = dateEncodingStrategy
encoder.dataEncodingStrategy = dataEncodingStrategy
encoder.nonConformingFloatEncodingStrategy = nonConformingFloatEncodingStrategy
payload = try encoder.encode(value)
} catch {
expectUnreachable("Failed to encode \(T.self) to JSON.")
}
if let expectedJSON = json {
expectEqual(expectedJSON, payload, "Produced JSON not identical to expected JSON.")
}
do {
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = dateDecodingStrategy
decoder.dataDecodingStrategy = dataDecodingStrategy
decoder.nonConformingFloatDecodingStrategy = nonConformingFloatDecodingStrategy
let decoded = try decoder.decode(T.self, from: payload)
expectEqual(decoded, value, "\(T.self) did not round-trip to an equal value.")
} catch {
expectUnreachable("Failed to decode \(T.self) from JSON.")
}
}
}
// MARK: - Helper Global Functions
func expectEqualPaths(_ lhs: [CodingKey?], _ rhs: [CodingKey?], _ prefix: String) {
if lhs.count != rhs.count {
expectUnreachable("\(prefix) [CodingKey?].count mismatch: \(lhs.count) != \(rhs.count)")
return
}
for (k1, k2) in zip(lhs, rhs) {
switch (k1, k2) {
case (.none, .none): continue
case (.some(let _k1), .none):
expectUnreachable("\(prefix) CodingKey mismatch: \(type(of: _k1)) != nil")
return
case (.none, .some(let _k2)):
expectUnreachable("\(prefix) CodingKey mismatch: nil != \(type(of: _k2))")
return
default: break
}
let key1 = k1!
let key2 = k2!
switch (key1.intValue, key2.intValue) {
case (.none, .none): break
case (.some(let i1), .none):
expectUnreachable("\(prefix) CodingKey.intValue mismatch: \(type(of: key1))(\(i1)) != nil")
return
case (.none, .some(let i2)):
expectUnreachable("\(prefix) CodingKey.intValue mismatch: nil != \(type(of: key2))(\(i2))")
return
case (.some(let i1), .some(let i2)):
guard i1 == i2 else {
expectUnreachable("\(prefix) CodingKey.intValue mismatch: \(type(of: key1))(\(i1)) != \(type(of: key2))(\(i2))")
return
}
break
}
expectEqual(key1.stringValue, key2.stringValue, "\(prefix) CodingKey.stringValue mismatch: \(type(of: key1))('\(key1.stringValue)') != \(type(of: key2))('\(key2.stringValue)')")
}
}
// MARK: - Test Types
/* FIXME: Import from %S/Inputs/Coding/SharedTypes.swift somehow. */
// MARK: - Empty Types
fileprivate struct EmptyStruct : Codable, Equatable {
static func ==(_ lhs: EmptyStruct, _ rhs: EmptyStruct) -> Bool {
return true
}
}
fileprivate class EmptyClass : Codable, Equatable {
static func ==(_ lhs: EmptyClass, _ rhs: EmptyClass) -> Bool {
return true
}
}
// MARK: - Single-Value Types
/// A simple on-off switch type that encodes as a single Bool value.
fileprivate enum Switch : Codable {
case off
case on
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
switch try container.decode(Bool.self) {
case false: self = .off
case true: self = .on
}
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
switch self {
case .off: try container.encode(false)
case .on: try container.encode(true)
}
}
}
/// A simple timestamp type that encodes as a single Double value.
fileprivate struct Timestamp : Codable {
let value: Double
init(_ value: Double) {
self.value = value
}
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
value = try container.decode(Double.self)
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(self.value)
}
}
/// A simple referential counter type that encodes as a single Int value.
fileprivate final class Counter : Codable {
var count: Int = 0
init() {}
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
count = try container.decode(Int.self)
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(self.count)
}
}
// MARK: - Structured Types
/// A simple address type that encodes as a dictionary of values.
fileprivate struct Address : Codable, Equatable {
let street: String
let city: String
let state: String
let zipCode: Int
let country: String
init(street: String, city: String, state: String, zipCode: Int, country: String) {
self.street = street
self.city = city
self.state = state
self.zipCode = zipCode
self.country = country
}
static func ==(_ lhs: Address, _ rhs: Address) -> Bool {
return lhs.street == rhs.street &&
lhs.city == rhs.city &&
lhs.state == rhs.state &&
lhs.zipCode == rhs.zipCode &&
lhs.country == rhs.country
}
static var testValue: Address {
return Address(street: "1 Infinite Loop",
city: "Cupertino",
state: "CA",
zipCode: 95014,
country: "United States")
}
}
/// A simple person class that encodes as a dictionary of values.
fileprivate class Person : Codable, Equatable {
let name: String
let email: String
init(name: String, email: String) {
self.name = name
self.email = email
}
static func ==(_ lhs: Person, _ rhs: Person) -> Bool {
return lhs.name == rhs.name && lhs.email == rhs.email
}
static var testValue: Person {
return Person(name: "Johnny Appleseed", email: "appleseed@apple.com")
}
}
/// A simple company struct which encodes as a dictionary of nested values.
fileprivate struct Company : Codable, Equatable {
let address: Address
var employees: [Person]
init(address: Address, employees: [Person]) {
self.address = address
self.employees = employees
}
static func ==(_ lhs: Company, _ rhs: Company) -> Bool {
return lhs.address == rhs.address && lhs.employees == rhs.employees
}
static var testValue: Company {
return Company(address: Address.testValue, employees: [Person.testValue])
}
}
// MARK: - Helper Types
/// Wraps a type T so that it can be encoded at the top level of a payload.
fileprivate struct TopLevelWrapper<T> : Codable, Equatable where T : Codable, T : Equatable {
let value: T
init(_ value: T) {
self.value = value
}
func encode(to encoder: Encoder) throws {
var container = encoder.unkeyedContainer()
try container.encode(value)
}
init(from decoder: Decoder) throws {
var container = try decoder.unkeyedContainer()
value = try container.decode(T.self)
assert(container.isAtEnd)
}
static func ==(_ lhs: TopLevelWrapper<T>, _ rhs: TopLevelWrapper<T>) -> Bool {
return lhs.value == rhs.value
}
}
fileprivate struct FloatNaNPlaceholder : Codable, Equatable {
init() {}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(Float.nan)
}
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
let float = try container.decode(Float.self)
if !float.isNaN {
throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Couldn't decode NaN."))
}
}
static func ==(_ lhs: FloatNaNPlaceholder, _ rhs: FloatNaNPlaceholder) -> Bool {
return true
}
}
fileprivate struct DoubleNaNPlaceholder : Codable, Equatable {
init() {}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(Double.nan)
}
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
let double = try container.decode(Double.self)
if !double.isNaN {
throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Couldn't decode NaN."))
}
}
static func ==(_ lhs: DoubleNaNPlaceholder, _ rhs: DoubleNaNPlaceholder) -> Bool {
return true
}
}
struct NestedContainersTestType : Encodable {
let testSuperEncoder: Bool
init(testSuperEncoder: Bool = false) {
self.testSuperEncoder = testSuperEncoder
}
enum TopLevelCodingKeys : Int, CodingKey {
case a
case b
case c
}
enum IntermediateCodingKeys : Int, CodingKey {
case one
case two
}
func encode(to encoder: Encoder) throws {
if self.testSuperEncoder {
var topLevelContainer = encoder.container(keyedBy: TopLevelCodingKeys.self)
expectEqualPaths(encoder.codingPath, [], "Top-level Encoder's codingPath changed.")
expectEqualPaths(topLevelContainer.codingPath, [], "New first-level keyed container has non-empty codingPath.")
let superEncoder = topLevelContainer.superEncoder(forKey: .a)
expectEqualPaths(encoder.codingPath, [], "Top-level Encoder's codingPath changed.")
expectEqualPaths(topLevelContainer.codingPath, [], "First-level keyed container's codingPath changed.")
expectEqualPaths(superEncoder.codingPath, [TopLevelCodingKeys.a], "New superEncoder had unexpected codingPath.")
_testNestedContainers(in: superEncoder, baseCodingPath: [TopLevelCodingKeys.a])
} else {
_testNestedContainers(in: encoder, baseCodingPath: [])
}
}
func _testNestedContainers(in encoder: Encoder, baseCodingPath: [CodingKey?]) {
expectEqualPaths(encoder.codingPath, baseCodingPath, "New encoder has non-empty codingPath.")
// codingPath should not change upon fetching a non-nested container.
var firstLevelContainer = encoder.container(keyedBy: TopLevelCodingKeys.self)
expectEqualPaths(encoder.codingPath, baseCodingPath, "Top-level Encoder's codingPath changed.")
expectEqualPaths(firstLevelContainer.codingPath, baseCodingPath, "New first-level keyed container has non-empty codingPath.")
// Nested Keyed Container
do {
// Nested container for key should have a new key pushed on.
var secondLevelContainer = firstLevelContainer.nestedContainer(keyedBy: IntermediateCodingKeys.self, forKey: .a)
expectEqualPaths(encoder.codingPath, baseCodingPath, "Top-level Encoder's codingPath changed.")
expectEqualPaths(firstLevelContainer.codingPath, baseCodingPath, "First-level keyed container's codingPath changed.")
expectEqualPaths(secondLevelContainer.codingPath, baseCodingPath + [TopLevelCodingKeys.a], "New second-level keyed container had unexpected codingPath.")
// Inserting a keyed container should not change existing coding paths.
let thirdLevelContainerKeyed = secondLevelContainer.nestedContainer(keyedBy: IntermediateCodingKeys.self, forKey: .one)
expectEqualPaths(encoder.codingPath, baseCodingPath, "Top-level Encoder's codingPath changed.")
expectEqualPaths(firstLevelContainer.codingPath, baseCodingPath, "First-level keyed container's codingPath changed.")
expectEqualPaths(secondLevelContainer.codingPath, baseCodingPath + [TopLevelCodingKeys.a], "Second-level keyed container's codingPath changed.")
expectEqualPaths(thirdLevelContainerKeyed.codingPath, baseCodingPath + [TopLevelCodingKeys.a, IntermediateCodingKeys.one], "New third-level keyed container had unexpected codingPath.")
// Inserting an unkeyed container should not change existing coding paths.
let thirdLevelContainerUnkeyed = secondLevelContainer.nestedUnkeyedContainer(forKey: .two)
expectEqualPaths(encoder.codingPath, baseCodingPath + [], "Top-level Encoder's codingPath changed.")
expectEqualPaths(firstLevelContainer.codingPath, baseCodingPath + [], "First-level keyed container's codingPath changed.")
expectEqualPaths(secondLevelContainer.codingPath, baseCodingPath + [TopLevelCodingKeys.a], "Second-level keyed container's codingPath changed.")
expectEqualPaths(thirdLevelContainerUnkeyed.codingPath, baseCodingPath + [TopLevelCodingKeys.a, IntermediateCodingKeys.two], "New third-level unkeyed container had unexpected codingPath.")
}
// Nested Unkeyed Container
do {
// Nested container for key should have a new key pushed on.
var secondLevelContainer = firstLevelContainer.nestedUnkeyedContainer(forKey: .a)
expectEqualPaths(encoder.codingPath, baseCodingPath, "Top-level Encoder's codingPath changed.")
expectEqualPaths(firstLevelContainer.codingPath, baseCodingPath, "First-level keyed container's codingPath changed.")
expectEqualPaths(secondLevelContainer.codingPath, baseCodingPath + [TopLevelCodingKeys.a], "New second-level keyed container had unexpected codingPath.")
// Appending a keyed container should not change existing coding paths.
let thirdLevelContainerKeyed = secondLevelContainer.nestedContainer(keyedBy: IntermediateCodingKeys.self)
expectEqualPaths(encoder.codingPath, baseCodingPath, "Top-level Encoder's codingPath changed.")
expectEqualPaths(firstLevelContainer.codingPath, baseCodingPath, "First-level keyed container's codingPath changed.")
expectEqualPaths(secondLevelContainer.codingPath, baseCodingPath + [TopLevelCodingKeys.a], "Second-level unkeyed container's codingPath changed.")
expectEqualPaths(thirdLevelContainerKeyed.codingPath, baseCodingPath + [TopLevelCodingKeys.a, nil], "New third-level keyed container had unexpected codingPath.")
// Appending an unkeyed container should not change existing coding paths.
let thirdLevelContainerUnkeyed = secondLevelContainer.nestedUnkeyedContainer()
expectEqualPaths(encoder.codingPath, baseCodingPath, "Top-level Encoder's codingPath changed.")
expectEqualPaths(firstLevelContainer.codingPath, baseCodingPath, "First-level keyed container's codingPath changed.")
expectEqualPaths(secondLevelContainer.codingPath, baseCodingPath + [TopLevelCodingKeys.a], "Second-level unkeyed container's codingPath changed.")
expectEqualPaths(thirdLevelContainerUnkeyed.codingPath, baseCodingPath + [TopLevelCodingKeys.a, nil], "New third-level unkeyed container had unexpected codingPath.")
}
}
}
// MARK: - Run Tests
#if !FOUNDATION_XCTEST
var JSONEncoderTests = TestSuite("TestJSONEncoder")
JSONEncoderTests.test("testEncodingTopLevelEmptyStruct") { TestJSONEncoder().testEncodingTopLevelEmptyStruct() }
JSONEncoderTests.test("testEncodingTopLevelEmptyClass") { TestJSONEncoder().testEncodingTopLevelEmptyClass() }
JSONEncoderTests.test("testEncodingTopLevelSingleValueEnum") { TestJSONEncoder().testEncodingTopLevelSingleValueEnum() }
JSONEncoderTests.test("testEncodingTopLevelSingleValueStruct") { TestJSONEncoder().testEncodingTopLevelSingleValueStruct() }
JSONEncoderTests.test("testEncodingTopLevelSingleValueClass") { TestJSONEncoder().testEncodingTopLevelSingleValueClass() }
JSONEncoderTests.test("testEncodingTopLevelStructuredStruct") { TestJSONEncoder().testEncodingTopLevelStructuredStruct() }
JSONEncoderTests.test("testEncodingTopLevelStructuredClass") { TestJSONEncoder().testEncodingTopLevelStructuredClass() }
JSONEncoderTests.test("testEncodingTopLevelStructuredClass") { TestJSONEncoder().testEncodingTopLevelStructuredClass() }
JSONEncoderTests.test("testEncodingTopLevelDeepStructuredType") { TestJSONEncoder().testEncodingTopLevelDeepStructuredType()}
JSONEncoderTests.test("testEncodingDate") { TestJSONEncoder().testEncodingDate() }
JSONEncoderTests.test("testEncodingDateSecondsSince1970") { TestJSONEncoder().testEncodingDateSecondsSince1970() }
JSONEncoderTests.test("testEncodingDateMillisecondsSince1970") { TestJSONEncoder().testEncodingDateMillisecondsSince1970() }
JSONEncoderTests.test("testEncodingDateISO8601") { TestJSONEncoder().testEncodingDateISO8601() }
JSONEncoderTests.test("testEncodingDateFormatted") { TestJSONEncoder().testEncodingDateFormatted() }
JSONEncoderTests.test("testEncodingDateCustom") { TestJSONEncoder().testEncodingDateCustom() }
JSONEncoderTests.test("testEncodingDateCustomEmpty") { TestJSONEncoder().testEncodingDateCustomEmpty() }
JSONEncoderTests.test("testEncodingBase64Data") { TestJSONEncoder().testEncodingBase64Data() }
JSONEncoderTests.test("testEncodingCustomData") { TestJSONEncoder().testEncodingCustomData() }
JSONEncoderTests.test("testEncodingCustomDataEmpty") { TestJSONEncoder().testEncodingCustomDataEmpty() }
JSONEncoderTests.test("testEncodingNonConformingFloats") { TestJSONEncoder().testEncodingNonConformingFloats() }
JSONEncoderTests.test("testEncodingNonConformingFloatStrings") { TestJSONEncoder().testEncodingNonConformingFloatStrings() }
JSONEncoderTests.test("testNestedContainerCodingPaths") { TestJSONEncoder().testNestedContainerCodingPaths() }
JSONEncoderTests.test("testSuperEncoderCodingPaths") { TestJSONEncoder().testSuperEncoderCodingPaths() }
runAllTests()
#endif