Merge remote-tracking branch 'origin/master' into swift-4.0-branch
diff --git a/Foundation.xcodeproj/project.pbxproj b/Foundation.xcodeproj/project.pbxproj
index 804c8bc..d216523 100644
--- a/Foundation.xcodeproj/project.pbxproj
+++ b/Foundation.xcodeproj/project.pbxproj
@@ -354,6 +354,7 @@
 		D51239DF1CD9DA0800D433EE /* CFSocket.c in Sources */ = {isa = PBXBuildFile; fileRef = 5B5D88E01BBC9B0300234F36 /* CFSocket.c */; };
 		D512D17C1CD883F00032E6A5 /* TestFileHandle.swift in Sources */ = {isa = PBXBuildFile; fileRef = D512D17B1CD883F00032E6A5 /* TestFileHandle.swift */; };
 		D5C40F331CDA1D460005690C /* TestNSOperationQueue.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5C40F321CDA1D460005690C /* TestNSOperationQueue.swift */; };
+		DCA8120B1F046D13000D0C86 /* TestCodable.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCA8120A1F046D13000D0C86 /* TestCodable.swift */; };
 		E1A03F361C4828650023AF4D /* PropertyList-1.0.dtd in Resources */ = {isa = PBXBuildFile; fileRef = E1A03F351C4828650023AF4D /* PropertyList-1.0.dtd */; };
 		E1A03F381C482C730023AF4D /* NSXMLDTDTestData.xml in Resources */ = {isa = PBXBuildFile; fileRef = E1A03F371C482C730023AF4D /* NSXMLDTDTestData.xml */; };
 		E1A3726F1C31EBFB0023AF4D /* NSXMLDocumentTestData.xml in Resources */ = {isa = PBXBuildFile; fileRef = E1A3726E1C31EBFB0023AF4D /* NSXMLDocumentTestData.xml */; };
@@ -822,6 +823,7 @@
 		D512D17B1CD883F00032E6A5 /* TestFileHandle.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestFileHandle.swift; sourceTree = "<group>"; };
 		D5C40F321CDA1D460005690C /* TestNSOperationQueue.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestNSOperationQueue.swift; sourceTree = "<group>"; };
 		D834F9931C31C4060023812A /* TestNSOrderedSet.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestNSOrderedSet.swift; sourceTree = "<group>"; };
+		DCA8120A1F046D13000D0C86 /* TestCodable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestCodable.swift; sourceTree = "<group>"; };
 		DCDBB8321C1768AC00313299 /* TestNSData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestNSData.swift; sourceTree = "<group>"; };
 		E1A03F351C4828650023AF4D /* PropertyList-1.0.dtd */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = "PropertyList-1.0.dtd"; sourceTree = "<group>"; };
 		E1A03F371C482C730023AF4D /* NSXMLDTDTestData.xml */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = NSXMLDTDTestData.xml; sourceTree = "<group>"; };
@@ -1423,8 +1425,7 @@
 				A058C2011E529CF100B07AA1 /* TestMassFormatter.swift */,
 				BF8E65301DC3B3CB005AB5C3 /* TestNotification.swift */,
 				3EA9D66F1EF0532D00B362D6 /* TestJSONEncoder.swift */,
-				159884911DCC877700E3314C /* TestNSHTTPCookieStorage.swift */,
-				D4FE895A1D703D1100DA7986 /* TestURLRequest.swift */,
+				DCA8120A1F046D13000D0C86 /* TestCodable.swift */,
 				C93559281C12C49F009FD6A9 /* TestNSAffineTransform.swift */,
 				EA66F63C1BF1619600136161 /* TestNSArray.swift */,
 				294E3C1C1CC5E19300E4F44C /* TestNSAttributedString.swift */,
@@ -2430,6 +2431,7 @@
 				5B13B3271C582D4C00651CE2 /* TestNSArray.swift in Sources */,
 				5B13B3461C582D4C00651CE2 /* TestProcess.swift in Sources */,
 				555683BD1C1250E70041D4C6 /* TestNSUserDefaults.swift in Sources */,
+				DCA8120B1F046D13000D0C86 /* TestCodable.swift in Sources */,
 				7900433B1CACD33E00ECCBF1 /* TestNSCompoundPredicate.swift in Sources */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
diff --git a/Foundation/CGFloat.swift b/Foundation/CGFloat.swift
index 03371f3..904600d 100644
--- a/Foundation/CGFloat.swift
+++ b/Foundation/CGFloat.swift
@@ -949,3 +949,35 @@
         return native._cVarArgAlignment
     }
 }
+
+extension CGFloat : Codable {
+    @_transparent
+    public init(from decoder: Decoder) throws {
+        let container = try decoder.singleValueContainer()
+        do {
+            self.native = try container.decode(NativeType.self)
+        } catch DecodingError.typeMismatch(let type, let context) {
+            // We may have encoded as a different type on a different platform. A
+            // strict fixed-format decoder may disallow a conversion, so let's try the
+            // other type.
+            do {
+                if NativeType.self == Float.self {
+                    self.native = NativeType(try container.decode(Double.self))
+                } else {
+                    self.native = NativeType(try container.decode(Float.self))
+                }
+            } catch {
+                // Failed to decode as the other type, too. This is neither a Float nor
+                // a Double. Throw the old error; we don't want to clobber the original
+                // info.
+                throw DecodingError.typeMismatch(type, context)
+            }
+        }
+    }
+    
+    @_transparent
+    public func encode(to encoder: Encoder) throws {
+        var container = encoder.singleValueContainer()
+        try container.encode(self.native)
+    }
+}
diff --git a/Foundation/CharacterSet.swift b/Foundation/CharacterSet.swift
index bb346f7..79948ec 100644
--- a/Foundation/CharacterSet.swift
+++ b/Foundation/CharacterSet.swift
@@ -1,4 +1,4 @@
- //===----------------------------------------------------------------------===//
+//===----------------------------------------------------------------------===//
 //
 // This source file is part of the Swift.org open source project
 //
@@ -13,7 +13,7 @@
 private func _utfRangeToNSRange(_ inRange : Range<UnicodeScalar>) -> NSRange {
     return NSMakeRange(Int(inRange.lowerBound.value), Int(inRange.upperBound.value - inRange.lowerBound.value))
 }
- 
+
 private func _utfRangeToNSRange(_ inRange : ClosedRange<UnicodeScalar>) -> NSRange {
     return NSMakeRange(Int(inRange.lowerBound.value), Int(inRange.upperBound.value - inRange.lowerBound.value + 1))
 }
@@ -57,9 +57,9 @@
     deinit {
         releaseWrappedObject()
     }
-
-
-   override func copy(with zone: NSZone? = nil) -> Any {
+    
+    
+    override func copy(with zone: NSZone? = nil) -> Any {
         return _mapUnmanaged { $0.copy(with: zone) }
     }
     
@@ -74,23 +74,23 @@
     override var bitmapRepresentation: Data {
         return _mapUnmanaged { $0.bitmapRepresentation }
     }
-
+    
     override var inverted : CharacterSet {
         return _mapUnmanaged { $0.inverted }
     }
-
+    
     override func hasMemberInPlane(_ thePlane: UInt8) -> Bool {
         return _mapUnmanaged {$0.hasMemberInPlane(thePlane) }
     }
-
+    
     override func characterIsMember(_ member: unichar) -> Bool {
         return _mapUnmanaged { $0.characterIsMember(member) }
     }
-
+    
     override func longCharacterIsMember(_ member: UInt32) -> Bool {
         return _mapUnmanaged { $0.longCharacterIsMember(member) }
     }
-
+    
     override func isSuperset(of other: CharacterSet) -> Bool {
         return _mapUnmanaged { $0.isSuperset(of: other) }
     }
@@ -367,7 +367,7 @@
     // -----
     // MARK: -
     // MARK: SetAlgebraType
-
+    
     /// Insert a `UnicodeScalar` representation of a character into the `CharacterSet`.
     ///
     /// `UnicodeScalar` values are available on `Swift.String.UnicodeScalarView`.
@@ -441,17 +441,17 @@
             $0.formIntersection(with: other)
         }
     }
-
+    
     /// Returns a `CharacterSet` created by removing elements in `other` from `self`.
     public func subtracting(_ other: CharacterSet) -> CharacterSet {
         return intersection(other.inverted)
     }
-
+    
     /// Sets the value to a `CharacterSet` created by removing elements in `other` from `self`.
     public mutating func subtract(_ other: CharacterSet) {
         self = subtracting(other)
     }
-
+    
     /// Returns an exclusive or of the `CharacterSet` with another `CharacterSet`.
     public func symmetricDifference(_ other: CharacterSet) -> CharacterSet {
         return union(other).subtracting(intersection(other))
@@ -466,7 +466,7 @@
     public func isSuperset(of other: CharacterSet) -> Bool {
         return _mapUnmanaged { $0.isSuperset(of: other) }
     }
-
+    
     /// Returns true if the two `CharacterSet`s are equal.
     public static func ==(lhs : CharacterSet, rhs: CharacterSet) -> Bool {
         return lhs._wrapped.isEqual(rhs._bridgeToObjectiveC()) // TODO: mlehew - as  NSCharacterSet
@@ -475,7 +475,7 @@
 
 
 // MARK: Objective-C Bridging
- extension CharacterSet : _ObjectTypeBridgeable {
+extension CharacterSet : _ObjectTypeBridgeable {
     public static func _isBridgedToObjectiveC() -> Bool {
         return true
     }
diff --git a/Foundation/IndexPath.swift b/Foundation/IndexPath.swift
index b85aaf6..748c03a 100644
--- a/Foundation/IndexPath.swift
+++ b/Foundation/IndexPath.swift
@@ -851,4 +851,3 @@
         }
     }
 }
-
diff --git a/Foundation/Locale.swift b/Foundation/Locale.swift
index 7b81c63..1544767 100644
--- a/Foundation/Locale.swift
+++ b/Foundation/Locale.swift
@@ -466,3 +466,20 @@
         return result!
     }
 }
+
+extension Locale : Codable {
+    private enum CodingKeys : Int, CodingKey {
+        case identifier
+    }
+    
+    public init(from decoder: Decoder) throws {
+        let container = try decoder.container(keyedBy: CodingKeys.self)
+        let identifier = try container.decode(String.self, forKey: .identifier)
+        self.init(identifier: identifier)
+    }
+    
+    public func encode(to encoder: Encoder) throws {
+        var container = encoder.container(keyedBy: CodingKeys.self)
+        try container.encode(self.identifier, forKey: .identifier)
+    }
+}
diff --git a/Foundation/NSAffineTransform.swift b/Foundation/NSAffineTransform.swift
index 0b3fc61..cfd0504 100644
--- a/Foundation/NSAffineTransform.swift
+++ b/Foundation/NSAffineTransform.swift
@@ -455,3 +455,25 @@
         return AffineTransform._unconditionallyBridgeFromObjectiveC(self)
     }
 }
+
+extension AffineTransform : Codable {
+    public init(from decoder: Decoder) throws {
+        var container = try decoder.unkeyedContainer()
+        m11 = try container.decode(CGFloat.self)
+        m12 = try container.decode(CGFloat.self)
+        m21 = try container.decode(CGFloat.self)
+        m22 = try container.decode(CGFloat.self)
+        tX  = try container.decode(CGFloat.self)
+        tY  = try container.decode(CGFloat.self)
+    }
+    
+    public func encode(to encoder: Encoder) throws {
+        var container = encoder.unkeyedContainer()
+        try container.encode(self.m11)
+        try container.encode(self.m12)
+        try container.encode(self.m21)
+        try container.encode(self.m22)
+        try container.encode(self.tX)
+        try container.encode(self.tY)
+    }
+}
diff --git a/Foundation/NSDecimal.swift b/Foundation/NSDecimal.swift
index 9529532..74eb4be 100644
--- a/Foundation/NSDecimal.swift
+++ b/Foundation/NSDecimal.swift
@@ -2086,3 +2086,58 @@
         return result
     }
 }
+
+extension Decimal : Codable {
+    private enum CodingKeys : Int, CodingKey {
+        case exponent
+        case length
+        case isNegative
+        case isCompact
+        case mantissa
+    }
+    
+    public init(from decoder: Decoder) throws {
+        let container = try decoder.container(keyedBy: CodingKeys.self)
+        let exponent = try container.decode(CInt.self, forKey: .exponent)
+        let length = try container.decode(CUnsignedInt.self, forKey: .length)
+        let isNegative = try container.decode(Bool.self, forKey: .isNegative)
+        let isCompact = try container.decode(Bool.self, forKey: .isCompact)
+        
+        var mantissaContainer = try container.nestedUnkeyedContainer(forKey: .mantissa)
+        var mantissa: (CUnsignedShort, CUnsignedShort, CUnsignedShort, CUnsignedShort,
+            CUnsignedShort, CUnsignedShort, CUnsignedShort, CUnsignedShort) = (0,0,0,0,0,0,0,0)
+        mantissa.0 = try mantissaContainer.decode(CUnsignedShort.self)
+        mantissa.1 = try mantissaContainer.decode(CUnsignedShort.self)
+        mantissa.2 = try mantissaContainer.decode(CUnsignedShort.self)
+        mantissa.3 = try mantissaContainer.decode(CUnsignedShort.self)
+        mantissa.4 = try mantissaContainer.decode(CUnsignedShort.self)
+        mantissa.5 = try mantissaContainer.decode(CUnsignedShort.self)
+        mantissa.6 = try mantissaContainer.decode(CUnsignedShort.self)
+        mantissa.7 = try mantissaContainer.decode(CUnsignedShort.self)
+        
+        self.init(_exponent: exponent,
+                  _length: length,
+                  _isNegative: CUnsignedInt(isNegative ? 1 : 0),
+                  _isCompact: CUnsignedInt(isCompact ? 1 : 0),
+                  _reserved: 0,
+                  _mantissa: mantissa)
+    }
+    
+    public func encode(to encoder: Encoder) throws {
+        var container = encoder.container(keyedBy: CodingKeys.self)
+        try container.encode(_exponent, forKey: .exponent)
+        try container.encode(_length, forKey: .length)
+        try container.encode(_isNegative == 0 ? false : true, forKey: .isNegative)
+        try container.encode(_isCompact == 0 ? false : true, forKey: .isCompact)
+        
+        var mantissaContainer = container.nestedUnkeyedContainer(forKey: .mantissa)
+        try mantissaContainer.encode(_mantissa.0)
+        try mantissaContainer.encode(_mantissa.1)
+        try mantissaContainer.encode(_mantissa.2)
+        try mantissaContainer.encode(_mantissa.3)
+        try mantissaContainer.encode(_mantissa.4)
+        try mantissaContainer.encode(_mantissa.5)
+        try mantissaContainer.encode(_mantissa.6)
+        try mantissaContainer.encode(_mantissa.7)
+    }
+}
diff --git a/Foundation/PersonNameComponents.swift b/Foundation/PersonNameComponents.swift
index 2b8a934..c9fc5a0 100644
--- a/Foundation/PersonNameComponents.swift
+++ b/Foundation/PersonNameComponents.swift
@@ -137,3 +137,36 @@
         return AnyHashable(self._bridgeToSwift())
     }
 }
+
+extension PersonNameComponents : Codable {
+    private enum CodingKeys : Int, CodingKey {
+        case namePrefix
+        case givenName
+        case middleName
+        case familyName
+        case nameSuffix
+        case nickname
+    }
+    
+    public init(from decoder: Decoder) throws {
+        self.init()
+        
+        let container = try decoder.container(keyedBy: CodingKeys.self)
+        self.namePrefix = try container.decodeIfPresent(String.self, forKey: .namePrefix)
+        self.givenName  = try container.decodeIfPresent(String.self, forKey: .givenName)
+        self.middleName = try container.decodeIfPresent(String.self, forKey: .middleName)
+        self.familyName = try container.decodeIfPresent(String.self, forKey: .familyName)
+        self.nameSuffix = try container.decodeIfPresent(String.self, forKey: .nameSuffix)
+        self.nickname   = try container.decodeIfPresent(String.self, forKey: .nickname)
+    }
+    
+    public func encode(to encoder: Encoder) throws {
+        var container = encoder.container(keyedBy: CodingKeys.self)
+        if let np = self.namePrefix { try container.encode(np, forKey: .namePrefix) }
+        if let gn = self.givenName  { try container.encode(gn, forKey: .givenName) }
+        if let mn = self.middleName { try container.encode(mn, forKey: .middleName) }
+        if let fn = self.familyName { try container.encode(fn, forKey: .familyName) }
+        if let ns = self.nameSuffix { try container.encode(ns, forKey: .nameSuffix) }
+        if let nn = self.nickname   { try container.encode(nn, forKey: .nickname) }
+    }
+}
diff --git a/Foundation/UUID.swift b/Foundation/UUID.swift
index 269a9a2..9b51302 100644
--- a/Foundation/UUID.swift
+++ b/Foundation/UUID.swift
@@ -163,3 +163,21 @@
     }
 }
 
+extension UUID : Codable {
+    public init(from decoder: Decoder) throws {
+        let container = try decoder.singleValueContainer()
+        let uuidString = try container.decode(String.self)
+        
+        guard let uuid = UUID(uuidString: uuidString) else {
+            throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: decoder.codingPath,
+                                                                    debugDescription: "Attempted to decode UUID from invalid UUID string."))
+        }
+        
+        self = uuid
+    }
+    
+    public func encode(to encoder: Encoder) throws {
+        var container = encoder.singleValueContainer()
+        try container.encode(self.uuidString)
+    }
+}
diff --git a/TestFoundation/TestCodable.swift b/TestFoundation/TestCodable.swift
new file mode 100644
index 0000000..23650d1
--- /dev/null
+++ b/TestFoundation/TestCodable.swift
@@ -0,0 +1,240 @@
+// This source file is part of the Swift.org open source project
+//
+// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors
+// Licensed under Apache License v2.0 with Runtime Library Exception
+//
+// See http://swift.org/LICENSE.txt for license information
+// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
+//
+
+#if DEPLOYMENT_RUNTIME_OBJC || os(Linux)
+import Foundation
+import XCTest
+#else
+import SwiftFoundation
+import SwiftXCTest
+#endif
+
+// MARK: - Helper Functions
+
+private func makePersonNameComponents(namePrefix: String? = nil,
+                                      givenName: String? = nil,
+                                      middleName: String? = nil,
+                                      familyName: String? = nil,
+                                      nameSuffix: String? = nil,
+                                      nickname: String? = nil) -> PersonNameComponents {
+    var result = PersonNameComponents()
+    result.namePrefix = namePrefix
+    result.givenName = givenName
+    result.middleName = middleName
+    result.familyName = familyName
+    result.nameSuffix = nameSuffix
+    result.nickname = nickname
+    return result
+}
+
+func expectRoundTripEquality<T : Codable>(of value: T, encode: (T) throws -> Data, decode: (Data) throws -> T) where T : Equatable {
+    let data: Data
+    do {
+        data = try encode(value)
+    } catch {
+        fatalError("Unable to encode \(T.self) <\(value)>: \(error)")
+    }
+
+    let decoded: T
+    do {
+        decoded = try decode(data)
+    } catch {
+        fatalError("Unable to decode \(T.self) <\(value)>: \(error)")
+    }
+
+    XCTAssertEqual(value, decoded, "Decoded \(T.self) <\(decoded)> not equal to original <\(value)>")
+}
+
+func expectRoundTripEqualityThroughJSON<T : Codable>(for value: T) where T : Equatable {
+    let encode = { (_ value: T) throws -> Data in
+        return try JSONEncoder().encode(value)
+    }
+
+    let decode = { (_ data: Data) throws -> T in
+        return try JSONDecoder().decode(T.self, from: data)
+    }
+
+    expectRoundTripEquality(of: value, encode: encode, decode: decode)
+}
+
+// MARK: - Helper Types
+// A wrapper around a UUID that will allow it to be encoded at the top level of an encoder.
+struct UUIDCodingWrapper : Codable, Equatable {
+    let value: UUID
+
+    init(_ value: UUID) {
+        self.value = value
+    }
+
+    static func ==(_ lhs: UUIDCodingWrapper, _ rhs: UUIDCodingWrapper) -> Bool {
+        return lhs.value == rhs.value
+    }
+}
+
+// MARK: - Tests
+class TestCodable : XCTestCase {
+
+    // MARK: - PersonNameComponents
+    lazy var personNameComponentsValues: [PersonNameComponents] = [
+        makePersonNameComponents(givenName: "John", familyName: "Appleseed"),
+        makePersonNameComponents(givenName: "John", familyName: "Appleseed", nickname: "Johnny"),
+        makePersonNameComponents(namePrefix: "Dr.", givenName: "Jane", middleName: "A.", familyName: "Appleseed", nameSuffix: "Esq.", nickname: "Janie")
+    ]
+
+    func test_PersonNameComponents_JSON() {
+        for components in personNameComponentsValues {
+            expectRoundTripEqualityThroughJSON(for: components)
+        }
+    }
+
+    // MARK: - UUID
+    lazy var uuidValues: [UUID] = [
+        UUID(),
+        UUID(uuidString: "E621E1F8-C36C-495A-93FC-0C247A3E6E5F")!,
+        UUID(uuidString: "e621e1f8-c36c-495a-93fc-0c247a3e6e5f")!,
+        UUID(uuid: uuid_t(0xe6,0x21,0xe1,0xf8,0xc3,0x6c,0x49,0x5a,0x93,0xfc,0x0c,0x24,0x7a,0x3e,0x6e,0x5f))
+    ]
+
+    func test_UUID_JSON() {
+        for uuid in uuidValues {
+            // We have to wrap the UUID since we cannot have a top-level string.
+            expectRoundTripEqualityThroughJSON(for: UUIDCodingWrapper(uuid))
+        }
+    }
+
+    // MARK: - URL
+    lazy var urlValues: [URL] = [
+        URL(fileURLWithPath: NSTemporaryDirectory()),
+        URL(fileURLWithPath: "/"),
+        URL(string: "http://apple.com")!,
+        URL(string: "swift", relativeTo: URL(string: "http://apple.com")!)!,
+        URL(fileURLWithPath: "bin/sh", relativeTo: URL(fileURLWithPath: "/"))
+    ]
+
+    func test_URL_JSON() {
+        for url in urlValues {
+            expectRoundTripEqualityThroughJSON(for: url)
+        }
+    }
+
+    // MARK: - NSRange
+    lazy var nsrangeValues: [NSRange] = [
+        NSRange(),
+        NSRange(location: 0, length: Int.max),
+        NSRange(location: NSNotFound, length: 0),
+        ]
+
+    func test_NSRange_JSON() {
+        for range in nsrangeValues {
+            expectRoundTripEqualityThroughJSON(for: range)
+        }
+    }
+
+    // MARK: - Locale
+    lazy var localeValues: [Locale] = [
+        Locale(identifier: ""),
+        Locale(identifier: "en"),
+        Locale(identifier: "en_US"),
+        Locale(identifier: "en_US_POSIX"),
+        Locale(identifier: "uk"),
+        Locale(identifier: "fr_FR"),
+        Locale(identifier: "fr_BE"),
+        Locale(identifier: "zh-Hant-HK")
+    ]
+
+    func test_Locale_JSON() {
+        for locale in localeValues {
+            expectRoundTripEqualityThroughJSON(for: locale)
+        }
+    }
+
+    // MARK: - IndexSet
+    lazy var indexSetValues: [IndexSet] = [
+        IndexSet(),
+        IndexSet(integer: 42),
+        IndexSet(integersIn: 0 ..< Int.max)
+    ]
+
+    func test_IndexSet_JSON() {
+        for indexSet in indexSetValues {
+            expectRoundTripEqualityThroughJSON(for: indexSet)
+        }
+    }
+
+    // MARK: - IndexPath
+    lazy var indexPathValues: [IndexPath] = [
+        IndexPath(), // empty
+        IndexPath(index: 0), // single
+        IndexPath(indexes: [1, 2]), // pair
+        IndexPath(indexes: [3, 4, 5, 6, 7, 8]), // array
+    ]
+
+    func test_IndexPath_JSON() {
+        for indexPath in indexPathValues {
+            expectRoundTripEqualityThroughJSON(for: indexPath)
+        }
+    }
+
+    // MARK: - AffineTransform
+    lazy var affineTransformValues: [AffineTransform] = [
+        AffineTransform.identity,
+        AffineTransform(),
+        AffineTransform(translationByX: 2.0, byY: 2.0),
+        AffineTransform(scale: 2.0),
+
+        // Disabled due to a bug: JSONSerialization loses precision for m12 and m21
+        // 0.02741213359204429 is serialized to 0.0274121335920443
+        //        AffineTransform(rotationByDegrees: .pi / 2),
+
+        AffineTransform(m11: 1.0, m12: 2.5, m21: 66.2, m22: 40.2, tX: -5.5, tY: 3.7),
+        AffineTransform(m11: -55.66, m12: 22.7, m21: 1.5, m22: 0.0, tX: -22, tY: -33),
+        AffineTransform(m11: 4.5, m12: 1.1, m21: 0.025, m22: 0.077, tX: -0.55, tY: 33.2),
+        AffineTransform(m11: 7.0, m12: -2.3, m21: 6.7, m22: 0.25, tX: 0.556, tY: 0.99),
+        AffineTransform(m11: 0.498, m12: -0.284, m21: -0.742, m22: 0.3248, tX: 12, tY: 44)
+    ]
+
+    func test_AffineTransform_JSON() {
+        for transform in affineTransformValues {
+            expectRoundTripEqualityThroughJSON(for: transform)
+        }
+    }
+
+    // MARK: - Decimal
+    lazy var decimalValues: [Decimal] = [
+        Decimal.leastFiniteMagnitude,
+        Decimal.greatestFiniteMagnitude,
+        Decimal.leastNormalMagnitude,
+        Decimal.leastNonzeroMagnitude,
+        Decimal.pi,
+        Decimal()
+    ]
+
+    func test_Decimal_JSON() {
+        for decimal in decimalValues {
+            expectRoundTripEqualityThroughJSON(for: decimal)
+        }
+    }
+
+}
+
+extension TestCodable {
+    static var allTests: [(String, (TestCodable) -> () throws -> Void)] {
+        return [
+            ("test_PersonNameComponents_JSON", test_PersonNameComponents_JSON),
+            ("test_UUID_JSON", test_UUID_JSON),
+            ("test_URL_JSON", test_URL_JSON),
+            ("test_NSRange_JSON", test_NSRange_JSON),
+            ("test_Locale_JSON", test_Locale_JSON),
+            ("test_IndexSet_JSON", test_IndexSet_JSON),
+            ("test_IndexPath_JSON", test_IndexPath_JSON),
+            ("test_AffineTransform_JSON", test_AffineTransform_JSON),
+            ("test_Decimal_JSON", test_Decimal_JSON),
+        ]
+    }
+}
diff --git a/TestFoundation/main.swift b/TestFoundation/main.swift
index ba459e2..5788e03 100644
--- a/TestFoundation/main.swift
+++ b/TestFoundation/main.swift
@@ -103,4 +103,5 @@
     testCase(TestNotification.allTests),
     testCase(TestMassFormatter.allTests),
     testCase(TestJSONEncoder.allTests),
+    testCase(TestCodable.allTests),
 ])