Skip to content

Commit 2ce58c1

Browse files
author
Itai Ferber
committed
Add JSON and property list encoders and decoders
* Integrate {JSON,PropertyList}{Encoder,Decoder} types to facilitate encoding types in JSON and property list formats * Adds Foundation-specific extensions to allow errors exposed from the stdlib to bridge to NSErrors
1 parent 0ecf8a9 commit 2ce58c1

File tree

5 files changed

+3555
-0
lines changed

5 files changed

+3555
-0
lines changed

stdlib/public/SDK/Foundation/CMakeLists.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ add_swift_library(swiftFoundation ${SWIFT_SDK_OVERLAY_LIBRARY_BUILD_TYPES} IS_SD
66
Boxing.swift
77
Calendar.swift
88
CharacterSet.swift
9+
Codable.swift
910
Data.swift
1011
DataThunks.m
1112
Date.swift
@@ -17,6 +18,7 @@ add_swift_library(swiftFoundation ${SWIFT_SDK_OVERLAY_LIBRARY_BUILD_TYPES} IS_SD
1718
Foundation.swift
1819
IndexPath.swift
1920
IndexSet.swift
21+
JSONEncoder.swift
2022
Locale.swift
2123
Measurement.swift
2224
Notification.swift
@@ -41,6 +43,7 @@ add_swift_library(swiftFoundation ${SWIFT_SDK_OVERLAY_LIBRARY_BUILD_TYPES} IS_SD
4143
NSURL.swift
4244
NSValue.swift.gyb
4345
PersonNameComponents.swift
46+
PlistEncoder.swift
4447
ReferenceConvertible.swift
4548
String.swift
4649
TimeZone.swift
Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
//===----------------------------------------------------------------------===//
14+
// Codable Extensions
15+
//===----------------------------------------------------------------------===//
16+
17+
extension Date : Codable {
18+
public init(from decoder: Decoder) throws {
19+
let timestamp = try decoder.singleValueContainer().decode(Double.self)
20+
self.init(timeIntervalSince1970: timestamp)
21+
}
22+
23+
public func encode(to encoder: Encoder) throws {
24+
var container = encoder.singleValueContainer()
25+
try container.encode(self.timeIntervalSince1970)
26+
}
27+
}
28+
29+
extension Data : Codable {
30+
private enum CodingKeys : Int, CodingKey {
31+
case length
32+
case bytes
33+
34+
// TODO: Remove these when derived conformance is merged.
35+
var stringValue: String {
36+
switch self {
37+
case .length: return "length"
38+
case .bytes: return "bytes"
39+
}
40+
}
41+
42+
init?(stringValue: String) {
43+
switch stringValue {
44+
case "length": self = .length
45+
case "bytes": self = .bytes
46+
default: return nil
47+
}
48+
}
49+
50+
var intValue: Int? {
51+
return self.rawValue
52+
}
53+
54+
init?(intValue: Int) {
55+
switch intValue {
56+
case 0: self = .length
57+
case 1: self = .bytes
58+
default: return nil
59+
}
60+
}
61+
}
62+
63+
public init(from decoder: Decoder) throws {
64+
let container = try decoder.container(keyedBy: CodingKeys.self)
65+
let length = try container.decode(Int.self, forKey: .length)
66+
guard length >= 0 else {
67+
throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: decoder.codingPath,
68+
debugDescription: "Cannot decode a Data of negative length \(length)."))
69+
}
70+
71+
var bytesContainer = try container.nestedUnkeyedContainer(forKey: .bytes)
72+
73+
self.init(capacity: length)
74+
for i in 0 ..< length {
75+
let byte = try bytesContainer.decode(UInt8.self)
76+
self[i] = byte
77+
}
78+
}
79+
80+
public func encode(to encoder: Encoder) throws {
81+
var container = encoder.container(keyedBy: CodingKeys.self)
82+
try container.encode(self.count, forKey: .length)
83+
84+
var bytesContainer = container.nestedUnkeyedContainer(forKey: .bytes)
85+
86+
// Since enumerateBytes does not rethrow, we need to catch the error, stow it away, and rethrow if we stopped.
87+
var caughtError: Error? = nil
88+
self.enumerateBytes { (buffer: UnsafeBufferPointer<UInt8>, byteIndex: Data.Index, stop: inout Bool) in
89+
do {
90+
for byte in buffer {
91+
try bytesContainer.encode(byte)
92+
}
93+
} catch {
94+
caughtError = error
95+
stop = true
96+
}
97+
}
98+
99+
if let error = caughtError {
100+
throw error
101+
}
102+
}
103+
}
104+
105+
//===----------------------------------------------------------------------===//
106+
// Errors
107+
//===----------------------------------------------------------------------===//
108+
109+
// Adding the following extensions to EncodingError and DecodingError allows them to bridge to NSErrors implicitly.
110+
111+
fileprivate let NSCodingPathErrorKey = "NSCodingPath"
112+
fileprivate let NSDebugDescriptionErrorKey = "NSDebugDescription"
113+
114+
extension EncodingError : CustomNSError {
115+
public static var errorDomain: String = NSCocoaErrorDomain
116+
117+
public var errorCode: Int {
118+
switch self {
119+
case .invalidValue(_, _): return CocoaError.coderInvalidValue.rawValue
120+
}
121+
}
122+
123+
public var errorUserInfo: [String : Any] {
124+
let context: Context
125+
switch self {
126+
case .invalidValue(_, let c): context = c
127+
}
128+
129+
return [NSCodingPathErrorKey: context.codingPath,
130+
NSDebugDescriptionErrorKey: context.debugDescription]
131+
}
132+
}
133+
134+
extension DecodingError : CustomNSError {
135+
public static var errorDomain: String = NSCocoaErrorDomain
136+
137+
public var errorCode: Int {
138+
switch self {
139+
case .valueNotFound(_, _): fallthrough
140+
case .keyNotFound(_, _):
141+
return CocoaError._coderValueNotFound.rawValue
142+
143+
case .typeMismatch(_, _): fallthrough
144+
case .dataCorrupted(_):
145+
return CocoaError._coderReadCorrupt.rawValue
146+
}
147+
}
148+
149+
public var errorUserInfo: [String : Any]? {
150+
let context: Context
151+
switch self {
152+
case .typeMismatch(_, let c): context = c
153+
case .valueNotFound(_, let c): context = c
154+
case .keyNotFound(_, let c): context = c
155+
case .dataCorrupted(let c): context = c
156+
}
157+
158+
return [NSCodingPathErrorKey: context.codingPath,
159+
NSDebugDescriptionErrorKey: context.debugDescription]
160+
}
161+
}
162+
163+
//===----------------------------------------------------------------------===//
164+
// Error Utilities
165+
//===----------------------------------------------------------------------===//
166+
167+
internal extension DecodingError {
168+
/// Returns a `.typeMismatch` error describing the expected type.
169+
///
170+
/// - parameter path: The path of `CodingKey`s taken to decode a value of this type.
171+
/// - parameter expectation: The type expected to be encountered.
172+
/// - parameter reality: The value that was encountered instead of the expected type.
173+
/// - returns: A `DecodingError` with the appropriate path and debug description.
174+
internal static func _typeMismatch(at path: [CodingKey?], expectation: Any.Type, reality: Any) -> DecodingError {
175+
let description = "Expected to decode \(expectation) but found \(_typeDescription(of: reality)) instead."
176+
return .typeMismatch(expectation, Context(codingPath: path, debugDescription: description))
177+
}
178+
179+
/// Returns a description of the type of `value` appropriate for an error message.
180+
///
181+
/// - parameter value: The value whose type to describe.
182+
/// - returns: A string describing `value`.
183+
/// - precondition: `value` is one of the types below.
184+
fileprivate static func _typeDescription(of value: Any) -> String {
185+
if value is NSNull {
186+
return "a null value"
187+
} else if value is NSNumber /* FIXME: If swift-corelibs-foundation isn't updated to use NSNumber, this check will be necessary: || value is Int || value is Double */ {
188+
return "a number"
189+
} else if value is String {
190+
return "a string/data"
191+
} else if value is [Any] {
192+
return "an array"
193+
} else if value is [String : Any] {
194+
return "a dictionary"
195+
} else {
196+
// This should never happen -- we somehow have a non-JSON type here.
197+
preconditionFailure("Invalid storage type \(type(of: value)).")
198+
}
199+
}
200+
}

0 commit comments

Comments
 (0)