Skip to content

Instantly share code, notes, and snippets.

@Qata
Last active April 18, 2019 00:27
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Qata/88cb35644e805f23abeba0b7bd0ceebc to your computer and use it in GitHub Desktop.
Save Qata/88cb35644e805f23abeba0b7bd0ceebc to your computer and use it in GitHub Desktop.
A JSONDecoder that wraps a JSONParser that isomorphically preserves the values of floating point numbers by treating all numbers as strings until instantiation of the exact type.
import Foundation
public indirect enum JSON: Equatable {
case null
case string(String)
case number(String)
case bool(Bool)
case dictionary([String: JSON])
case array([JSON])
}
import Foundation
open class JSONDecoder {
// MARK: Options
/// The strategy to use for decoding `Date` values.
public enum DateDecodingStrategy {
/// Defer to `Date` for decoding. This is the default strategy.
case deferredToDate
/// Decode the `Date` as a UNIX timestamp from a JSON number.
case secondsSince1970
/// Decode the `Date` as UNIX millisecond timestamp from a JSON number.
case millisecondsSince1970
/// Decode the `Date` as an ISO-8601-formatted string (in RFC 3339 format).
@available(macOS 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *)
case iso8601
/// Decode the `Date` as a string parsed by the given formatter.
case formatted(DateFormatter)
/// Decode the `Date` as a custom value decoded by the given closure.
case custom((_ decoder: Decoder) throws -> Date)
}
/// The strategy to use for decoding `Data` values.
public enum DataDecodingStrategy {
/// Defer to `Data` for decoding.
case deferredToData
/// Decode the `Data` from a Base64-encoded string. This is the default strategy.
case base64
/// Decode the `Data` as a custom value decoded by the given closure.
case custom((_ decoder: Decoder) throws -> Data)
}
/// The strategy to use for non-JSON-conforming floating-point values (IEEE 754 infinity and NaN).
public enum NonConformingFloatDecodingStrategy {
/// Throw upon encountering non-conforming values. This is the default strategy.
case `throw`
/// Decode the values from the given representation strings.
case convertFromString(positiveInfinity: String, negativeInfinity: String, nan: String)
}
/// The strategy to use for automatically changing the value of keys before decoding.
public enum KeyDecodingStrategy {
/// Use the keys specified by each type. This is the default strategy.
case useDefaultKeys
/// Convert from "snake_case_keys" to "camelCaseKeys" before attempting to match a key with the one specified by each type.
///
/// The conversion to upper case uses `Locale.system`, also known as the ICU "root" locale. This means the result is consistent regardless of the current user's locale and language preferences.
///
/// Converting from snake case to camel case:
/// 1. Capitalizes the word starting after each `_`
/// 2. Removes all `_`
/// 3. Preserves starting and ending `_` (as these are often used to indicate private variables or other metadata).
/// For example, `one_two_three` becomes `oneTwoThree`. `_one_two_three_` becomes `_oneTwoThree_`.
///
/// - Note: Using a key decoding strategy has a nominal performance cost, as each string key has to be inspected for the `_` character.
case convertFromSnakeCase
/// Provide a custom conversion from the key in the encoded JSON to the keys specified by the decoded types.
/// The full path to the current decoding position is provided for context (in case you need to locate this key within the payload). The returned key is used in place of the last component in the coding path before decoding.
/// If the result of the conversion is a duplicate key, then only one value will be present in the container for the type to decode from.
case custom((_ codingPath: [CodingKey]) -> CodingKey)
fileprivate static func _convertFromSnakeCase(_ stringKey: String) -> String {
guard !stringKey.isEmpty else { return stringKey }
// Find the first non-underscore character
guard let firstNonUnderscore = stringKey.firstIndex(where: { $0 != "_" }) else {
// Reached the end without finding an _
return stringKey
}
// Find the last non-underscore character
var lastNonUnderscore = stringKey.index(before: stringKey.endIndex)
while lastNonUnderscore > firstNonUnderscore && stringKey[lastNonUnderscore] == "_" {
stringKey.formIndex(before: &lastNonUnderscore)
}
let keyRange = firstNonUnderscore...lastNonUnderscore
let leadingUnderscoreRange = stringKey.startIndex..<firstNonUnderscore
let trailingUnderscoreRange = stringKey.index(after: lastNonUnderscore)..<stringKey.endIndex
var components = stringKey[keyRange].split(separator: "_")
let joinedString : String
if components.count == 1 {
// No underscores in key, leave the word as is - maybe already camel cased
joinedString = String(stringKey[keyRange])
} else {
joinedString = ([components[0].lowercased()] + components[1...].map { $0.capitalized }).joined()
}
// Do a cheap isEmpty check before creating and appending potentially empty strings
let result : String
if (leadingUnderscoreRange.isEmpty && trailingUnderscoreRange.isEmpty) {
result = joinedString
} else if (!leadingUnderscoreRange.isEmpty && !trailingUnderscoreRange.isEmpty) {
// Both leading and trailing underscores
result = String(stringKey[leadingUnderscoreRange]) + joinedString + String(stringKey[trailingUnderscoreRange])
} else if (!leadingUnderscoreRange.isEmpty) {
// Just leading
result = String(stringKey[leadingUnderscoreRange]) + joinedString
} else {
// Just trailing
result = joinedString + String(stringKey[trailingUnderscoreRange])
}
return result
}
}
/// The strategy to use in decoding dates. Defaults to `.deferredToDate`.
open var dateDecodingStrategy: DateDecodingStrategy = .deferredToDate
/// The strategy to use in decoding binary data. Defaults to `.base64`.
open var dataDecodingStrategy: DataDecodingStrategy = .base64
/// The strategy to use in decoding non-conforming numbers. Defaults to `.throw`.
open var nonConformingFloatDecodingStrategy: NonConformingFloatDecodingStrategy = .throw
/// The strategy to use for decoding keys. Defaults to `.useDefaultKeys`.
open var keyDecodingStrategy: KeyDecodingStrategy = .useDefaultKeys
/// Contextual user-provided information for use during decoding.
open var userInfo: [CodingUserInfoKey : Any] = [:]
/// Options set on the top-level encoder to pass down the decoding hierarchy.
fileprivate struct _Options {
let dateDecodingStrategy: DateDecodingStrategy
let dataDecodingStrategy: DataDecodingStrategy
let nonConformingFloatDecodingStrategy: NonConformingFloatDecodingStrategy
let keyDecodingStrategy: KeyDecodingStrategy
let userInfo: [CodingUserInfoKey : Any]
}
/// The options set on the top-level decoder.
fileprivate var options: _Options {
return _Options(dateDecodingStrategy: dateDecodingStrategy,
dataDecodingStrategy: dataDecodingStrategy,
nonConformingFloatDecodingStrategy: nonConformingFloatDecodingStrategy,
keyDecodingStrategy: keyDecodingStrategy,
userInfo: userInfo)
}
// MARK: - Constructing a JSON Decoder
/// Initializes `self` with default strategies.
public init() {}
// MARK: - Decoding Values
/// Decodes a top-level value of the given type from the given JSON representation.
///
/// - parameter type: The type of the value to decode.
/// - parameter data: The data to decode from.
/// - returns: A value of the requested type.
/// - throws: `DecodingError.dataCorrupted` if values requested from the payload are corrupted, or if the given data is not valid JSON.
/// - throws: An error if any value throws an error during decoding.
open func decode<T : Decodable>(_ type: T.Type, from data: Data) throws -> T {
switch JSONParser.parse(data: data) {
case let .failure(error):
throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: [], debugDescription: "The given data was not valid JSON.", underlyingError: error))
case let .success(topLevel):
let decoder = _JSONDecoder(referencing: topLevel, options: self.options)
guard let value = try decoder.unbox(topLevel, as: type) else {
throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: [], debugDescription: "The given data did not contain a top-level value."))
}
return value
}
}
}
fileprivate class _JSONDecoder : Decoder {
// MARK: Properties
/// The decoder's storage.
fileprivate var storage: _JSONDecodingStorage
private let numberFormatter = NumberFormatter()
/// Options set on the top-level decoder.
fileprivate let options: JSONDecoder._Options
/// The path to the current point in encoding.
fileprivate(set) public var codingPath: [CodingKey]
/// Contextual user-provided information for use during encoding.
public var userInfo: [CodingUserInfoKey : Any] {
return self.options.userInfo
}
// MARK: - Initialization
/// Initializes `self` with the given top-level container and options.
fileprivate init(referencing container: JSON, at codingPath: [CodingKey] = [], options: JSONDecoder._Options) {
self.storage = _JSONDecodingStorage()
self.storage.push(container: container)
self.codingPath = codingPath
self.options = options
}
// MARK: - Decoder Methods
public func container<Key>(keyedBy type: Key.Type) throws -> KeyedDecodingContainer<Key> {
guard self.storage.topContainer != .null else {
throw DecodingError.valueNotFound(KeyedDecodingContainer<Key>.self,
DecodingError.Context(codingPath: self.codingPath,
debugDescription: "Cannot get keyed decoding container -- found null value instead."))
}
guard case let .dictionary(dictionary) = self.storage.topContainer else {
throw DecodingError._typeMismatch(at: self.codingPath, expectation: [String : Any].self, reality: self.storage.topContainer)
}
let container = _JSONKeyedDecodingContainer<Key>(referencing: self, wrapping: dictionary)
return KeyedDecodingContainer(container)
}
public func unkeyedContainer() throws -> UnkeyedDecodingContainer {
guard self.storage.topContainer != .null else {
throw DecodingError.valueNotFound(UnkeyedDecodingContainer.self,
DecodingError.Context(codingPath: self.codingPath,
debugDescription: "Cannot get unkeyed decoding container -- found null value instead."))
}
guard case let .array(topContainer) = self.storage.topContainer else {
throw DecodingError._typeMismatch(at: self.codingPath, expectation: [Any].self, reality: self.storage.topContainer)
}
return _JSONUnkeyedDecodingContainer(referencing: self, wrapping: topContainer)
}
public func singleValueContainer() throws -> SingleValueDecodingContainer {
return self
}
}
fileprivate struct _JSONDecodingStorage {
// MARK: Properties
/// The container stack.
/// Elements may be any one of the JSON types (NSNull, NSNumber, String, Array, [String : Any]).
private(set) fileprivate var containers: [JSON] = []
// MARK: - Initialization
/// Initializes `self` with no containers.
fileprivate init() {}
// MARK: - Modifying the Stack
fileprivate var count: Int {
return self.containers.count
}
fileprivate var topContainer: JSON {
precondition(!self.containers.isEmpty, "Empty container stack.")
return self.containers.last!
}
fileprivate mutating func push(container: JSON) {
self.containers.append(container)
}
fileprivate mutating func popContainer() {
precondition(!self.containers.isEmpty, "Empty container stack.")
self.containers.removeLast()
}
}
fileprivate struct _JSONKeyedDecodingContainer<K : CodingKey> : KeyedDecodingContainerProtocol {
typealias Key = K
// MARK: Properties
/// A reference to the decoder we're reading from.
private let decoder: _JSONDecoder
/// A reference to the container we're reading from.
private let container: [String : JSON]
/// The path of coding keys taken to get to this point in decoding.
private(set) public var codingPath: [CodingKey]
// MARK: - Initialization
/// Initializes `self` by referencing the given decoder and container.
fileprivate init(referencing decoder: _JSONDecoder, wrapping container: [String : JSON]) {
self.decoder = decoder
switch decoder.options.keyDecodingStrategy {
case .useDefaultKeys:
self.container = container
case .convertFromSnakeCase:
// Convert the snake case keys in the container to camel case.
// If we hit a duplicate key after conversion, then we'll use the first one we saw. Effectively an undefined behavior with JSON dictionaries.
self.container = Dictionary(container.map {
key, value in (JSONDecoder.KeyDecodingStrategy._convertFromSnakeCase(key), value)
}, uniquingKeysWith: { (first, _) in first })
case .custom(let converter):
self.container = Dictionary(container.map {
key, value in (converter(decoder.codingPath + [_JSONKey(stringValue: key, intValue: nil)]).stringValue, value)
}, uniquingKeysWith: { (first, _) in first })
}
self.codingPath = decoder.codingPath
}
// MARK: - KeyedDecodingContainerProtocol Methods
public var allKeys: [Key] {
return self.container.keys.compactMap { Key(stringValue: $0) }
}
public func contains(_ key: Key) -> Bool {
return self.container[key.stringValue] != nil
}
private func _errorDescription(of key: CodingKey) -> String {
return "\(key) (\"\(key.stringValue)\")"
}
public func decodeNil(forKey key: Key) throws -> Bool {
guard let entry = self.container[key.stringValue] else {
throw DecodingError.keyNotFound(key, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "No value associated with key \(_errorDescription(of: key))."))
}
return entry == .null
}
public func decode(_ type: Bool.Type, forKey key: Key) throws -> Bool {
guard let entry = self.container[key.stringValue] else {
throw DecodingError.keyNotFound(key, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "No value associated with key \(_errorDescription(of: key))."))
}
self.decoder.codingPath.append(key)
defer { self.decoder.codingPath.removeLast() }
guard let value = try self.decoder.unbox(entry, as: Bool.self) else {
throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "Expected \(type) value but found null instead."))
}
return value
}
public func decode(_ type: Int.Type, forKey key: Key) throws -> Int {
guard let entry = self.container[key.stringValue] else {
throw DecodingError.keyNotFound(key, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "No value associated with key \(_errorDescription(of: key))."))
}
self.decoder.codingPath.append(key)
defer { self.decoder.codingPath.removeLast() }
guard let value = try self.decoder.unbox(entry, as: Int.self) else {
throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "Expected \(type) value but found null instead."))
}
return value
}
public func decode(_ type: Int8.Type, forKey key: Key) throws -> Int8 {
guard let entry = self.container[key.stringValue] else {
throw DecodingError.keyNotFound(key, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "No value associated with key \(_errorDescription(of: key))."))
}
self.decoder.codingPath.append(key)
defer { self.decoder.codingPath.removeLast() }
guard let value = try self.decoder.unbox(entry, as: Int8.self) else {
throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "Expected \(type) value but found null instead."))
}
return value
}
public func decode(_ type: Int16.Type, forKey key: Key) throws -> Int16 {
guard let entry = self.container[key.stringValue] else {
throw DecodingError.keyNotFound(key, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "No value associated with key \(_errorDescription(of: key))."))
}
self.decoder.codingPath.append(key)
defer { self.decoder.codingPath.removeLast() }
guard let value = try self.decoder.unbox(entry, as: Int16.self) else {
throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "Expected \(type) value but found null instead."))
}
return value
}
public func decode(_ type: Int32.Type, forKey key: Key) throws -> Int32 {
guard let entry = self.container[key.stringValue] else {
throw DecodingError.keyNotFound(key, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "No value associated with key \(_errorDescription(of: key))."))
}
self.decoder.codingPath.append(key)
defer { self.decoder.codingPath.removeLast() }
guard let value = try self.decoder.unbox(entry, as: Int32.self) else {
throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "Expected \(type) value but found null instead."))
}
return value
}
public func decode(_ type: Int64.Type, forKey key: Key) throws -> Int64 {
guard let entry = self.container[key.stringValue] else {
throw DecodingError.keyNotFound(key, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "No value associated with key \(_errorDescription(of: key))."))
}
self.decoder.codingPath.append(key)
defer { self.decoder.codingPath.removeLast() }
guard let value = try self.decoder.unbox(entry, as: Int64.self) else {
throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "Expected \(type) value but found null instead."))
}
return value
}
public func decode(_ type: UInt.Type, forKey key: Key) throws -> UInt {
guard let entry = self.container[key.stringValue] else {
throw DecodingError.keyNotFound(key, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "No value associated with key \(_errorDescription(of: key))."))
}
self.decoder.codingPath.append(key)
defer { self.decoder.codingPath.removeLast() }
guard let value = try self.decoder.unbox(entry, as: UInt.self) else {
throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "Expected \(type) value but found null instead."))
}
return value
}
public func decode(_ type: UInt8.Type, forKey key: Key) throws -> UInt8 {
guard let entry = self.container[key.stringValue] else {
throw DecodingError.keyNotFound(key, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "No value associated with key \(_errorDescription(of: key))."))
}
self.decoder.codingPath.append(key)
defer { self.decoder.codingPath.removeLast() }
guard let value = try self.decoder.unbox(entry, as: UInt8.self) else {
throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "Expected \(type) value but found null instead."))
}
return value
}
public func decode(_ type: UInt16.Type, forKey key: Key) throws -> UInt16 {
guard let entry = self.container[key.stringValue] else {
throw DecodingError.keyNotFound(key, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "No value associated with key \(_errorDescription(of: key))."))
}
self.decoder.codingPath.append(key)
defer { self.decoder.codingPath.removeLast() }
guard let value = try self.decoder.unbox(entry, as: UInt16.self) else {
throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "Expected \(type) value but found null instead."))
}
return value
}
public func decode(_ type: UInt32.Type, forKey key: Key) throws -> UInt32 {
guard let entry = self.container[key.stringValue] else {
throw DecodingError.keyNotFound(key, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "No value associated with key \(_errorDescription(of: key))."))
}
self.decoder.codingPath.append(key)
defer { self.decoder.codingPath.removeLast() }
guard let value = try self.decoder.unbox(entry, as: UInt32.self) else {
throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "Expected \(type) value but found null instead."))
}
return value
}
public func decode(_ type: UInt64.Type, forKey key: Key) throws -> UInt64 {
guard let entry = self.container[key.stringValue] else {
throw DecodingError.keyNotFound(key, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "No value associated with key \(_errorDescription(of: key))."))
}
self.decoder.codingPath.append(key)
defer { self.decoder.codingPath.removeLast() }
guard let value = try self.decoder.unbox(entry, as: UInt64.self) else {
throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "Expected \(type) value but found null instead."))
}
return value
}
public func decode(_ type: Float.Type, forKey key: Key) throws -> Float {
guard let entry = self.container[key.stringValue] else {
throw DecodingError.keyNotFound(key, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "No value associated with key \(_errorDescription(of: key))."))
}
self.decoder.codingPath.append(key)
defer { self.decoder.codingPath.removeLast() }
guard let value = try self.decoder.unbox(entry, as: Float.self) else {
throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "Expected \(type) value but found null instead."))
}
return value
}
public func decode(_ type: Double.Type, forKey key: Key) throws -> Double {
guard let entry = self.container[key.stringValue] else {
throw DecodingError.keyNotFound(key, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "No value associated with key \(_errorDescription(of: key))."))
}
self.decoder.codingPath.append(key)
defer { self.decoder.codingPath.removeLast() }
guard let value = try self.decoder.unbox(entry, as: Double.self) else {
throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "Expected \(type) value but found null instead."))
}
return value
}
public func decode(_ type: String.Type, forKey key: Key) throws -> String {
guard let entry = self.container[key.stringValue] else {
throw DecodingError.keyNotFound(key, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "No value associated with key \(_errorDescription(of: key))."))
}
self.decoder.codingPath.append(key)
defer { self.decoder.codingPath.removeLast() }
guard let value = try self.decoder.unbox(entry, as: String.self) else {
throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "Expected \(type) value but found null instead."))
}
return value
}
public func decode<T : Decodable>(_ type: T.Type, forKey key: Key) throws -> T {
guard let entry = self.container[key.stringValue] else {
throw DecodingError.keyNotFound(key, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "No value associated with key \(_errorDescription(of: key))."))
}
self.decoder.codingPath.append(key)
defer { self.decoder.codingPath.removeLast() }
guard let value = try self.decoder.unbox(entry, as: type) else {
throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath, debugDescription: "Expected \(type) value but found null instead."))
}
return value
}
public func nestedContainer<NestedKey>(keyedBy type: NestedKey.Type, forKey key: Key) throws -> KeyedDecodingContainer<NestedKey> {
self.decoder.codingPath.append(key)
defer { self.decoder.codingPath.removeLast() }
guard let value = self.container[key.stringValue] else {
throw DecodingError.keyNotFound(key,
DecodingError.Context(codingPath: self.codingPath,
debugDescription: "Cannot get \(KeyedDecodingContainer<NestedKey>.self) -- no value found for key \(_errorDescription(of: key))"))
}
guard case let .dictionary(dictionary) = value else {
throw DecodingError._typeMismatch(at: self.codingPath, expectation: [String : Any].self, reality: value)
}
let container = _JSONKeyedDecodingContainer<NestedKey>(referencing: self.decoder, wrapping: dictionary)
return KeyedDecodingContainer(container)
}
public func nestedUnkeyedContainer(forKey key: Key) throws -> UnkeyedDecodingContainer {
self.decoder.codingPath.append(key)
defer { self.decoder.codingPath.removeLast() }
guard let value = self.container[key.stringValue] else {
throw DecodingError.keyNotFound(key,
DecodingError.Context(codingPath: self.codingPath,
debugDescription: "Cannot get UnkeyedDecodingContainer -- no value found for key \(_errorDescription(of: key))"))
}
guard case let .array(array) = value else {
throw DecodingError._typeMismatch(at: self.codingPath, expectation: [Any].self, reality: value)
}
return _JSONUnkeyedDecodingContainer(referencing: self.decoder, wrapping: array)
}
private func _superDecoder(forKey key: CodingKey) throws -> Decoder {
self.decoder.codingPath.append(key)
defer { self.decoder.codingPath.removeLast() }
let value = self.container[key.stringValue, default: .null]
return _JSONDecoder(referencing: value, at: self.decoder.codingPath, options: self.decoder.options)
}
public func superDecoder() throws -> Decoder {
return try _superDecoder(forKey: _JSONKey.super)
}
public func superDecoder(forKey key: Key) throws -> Decoder {
return try _superDecoder(forKey: key)
}
}
fileprivate struct _JSONUnkeyedDecodingContainer : UnkeyedDecodingContainer {
// MARK: Properties
/// A reference to the decoder we're reading from.
private let decoder: _JSONDecoder
/// A reference to the container we're reading from.
private let container: [JSON]
/// The path of coding keys taken to get to this point in decoding.
private(set) public var codingPath: [CodingKey]
/// The index of the element we're about to decode.
private(set) public var currentIndex: Int
// MARK: - Initialization
/// Initializes `self` by referencing the given decoder and container.
fileprivate init(referencing decoder: _JSONDecoder, wrapping container: [JSON]) {
self.decoder = decoder
self.container = container
self.codingPath = decoder.codingPath
self.currentIndex = 0
}
// MARK: - UnkeyedDecodingContainer Methods
public var count: Int? {
return self.container.count
}
public var isAtEnd: Bool {
return self.currentIndex >= self.count!
}
public mutating func decodeNil() throws -> Bool {
guard !self.isAtEnd else {
throw DecodingError.valueNotFound(Any?.self, DecodingError.Context(codingPath: self.decoder.codingPath + [_JSONKey(index: self.currentIndex)], debugDescription: "Unkeyed container is at end."))
}
if self.container[self.currentIndex] == .null {
self.currentIndex += 1
return true
} else {
return false
}
}
public mutating func decode(_ type: Bool.Type) throws -> Bool {
guard !self.isAtEnd else {
throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath + [_JSONKey(index: self.currentIndex)], debugDescription: "Unkeyed container is at end."))
}
self.decoder.codingPath.append(_JSONKey(index: self.currentIndex))
defer { self.decoder.codingPath.removeLast() }
guard let decoded = try self.decoder.unbox(self.container[self.currentIndex], as: Bool.self) else {
throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath + [_JSONKey(index: self.currentIndex)], debugDescription: "Expected \(type) but found null instead."))
}
self.currentIndex += 1
return decoded
}
public mutating func decode(_ type: Int.Type) throws -> Int {
guard !self.isAtEnd else {
throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath + [_JSONKey(index: self.currentIndex)], debugDescription: "Unkeyed container is at end."))
}
self.decoder.codingPath.append(_JSONKey(index: self.currentIndex))
defer { self.decoder.codingPath.removeLast() }
guard let decoded = try self.decoder.unbox(self.container[self.currentIndex], as: Int.self) else {
throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath + [_JSONKey(index: self.currentIndex)], debugDescription: "Expected \(type) but found null instead."))
}
self.currentIndex += 1
return decoded
}
public mutating func decode(_ type: Int8.Type) throws -> Int8 {
guard !self.isAtEnd else {
throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath + [_JSONKey(index: self.currentIndex)], debugDescription: "Unkeyed container is at end."))
}
self.decoder.codingPath.append(_JSONKey(index: self.currentIndex))
defer { self.decoder.codingPath.removeLast() }
guard let decoded = try self.decoder.unbox(self.container[self.currentIndex], as: Int8.self) else {
throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath + [_JSONKey(index: self.currentIndex)], debugDescription: "Expected \(type) but found null instead."))
}
self.currentIndex += 1
return decoded
}
public mutating func decode(_ type: Int16.Type) throws -> Int16 {
guard !self.isAtEnd else {
throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath + [_JSONKey(index: self.currentIndex)], debugDescription: "Unkeyed container is at end."))
}
self.decoder.codingPath.append(_JSONKey(index: self.currentIndex))
defer { self.decoder.codingPath.removeLast() }
guard let decoded = try self.decoder.unbox(self.container[self.currentIndex], as: Int16.self) else {
throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath + [_JSONKey(index: self.currentIndex)], debugDescription: "Expected \(type) but found null instead."))
}
self.currentIndex += 1
return decoded
}
public mutating func decode(_ type: Int32.Type) throws -> Int32 {
guard !self.isAtEnd else {
throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath + [_JSONKey(index: self.currentIndex)], debugDescription: "Unkeyed container is at end."))
}
self.decoder.codingPath.append(_JSONKey(index: self.currentIndex))
defer { self.decoder.codingPath.removeLast() }
guard let decoded = try self.decoder.unbox(self.container[self.currentIndex], as: Int32.self) else {
throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath + [_JSONKey(index: self.currentIndex)], debugDescription: "Expected \(type) but found null instead."))
}
self.currentIndex += 1
return decoded
}
public mutating func decode(_ type: Int64.Type) throws -> Int64 {
guard !self.isAtEnd else {
throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath + [_JSONKey(index: self.currentIndex)], debugDescription: "Unkeyed container is at end."))
}
self.decoder.codingPath.append(_JSONKey(index: self.currentIndex))
defer { self.decoder.codingPath.removeLast() }
guard let decoded = try self.decoder.unbox(self.container[self.currentIndex], as: Int64.self) else {
throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath + [_JSONKey(index: self.currentIndex)], debugDescription: "Expected \(type) but found null instead."))
}
self.currentIndex += 1
return decoded
}
public mutating func decode(_ type: UInt.Type) throws -> UInt {
guard !self.isAtEnd else {
throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath + [_JSONKey(index: self.currentIndex)], debugDescription: "Unkeyed container is at end."))
}
self.decoder.codingPath.append(_JSONKey(index: self.currentIndex))
defer { self.decoder.codingPath.removeLast() }
guard let decoded = try self.decoder.unbox(self.container[self.currentIndex], as: UInt.self) else {
throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath + [_JSONKey(index: self.currentIndex)], debugDescription: "Expected \(type) but found null instead."))
}
self.currentIndex += 1
return decoded
}
public mutating func decode(_ type: UInt8.Type) throws -> UInt8 {
guard !self.isAtEnd else {
throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath + [_JSONKey(index: self.currentIndex)], debugDescription: "Unkeyed container is at end."))
}
self.decoder.codingPath.append(_JSONKey(index: self.currentIndex))
defer { self.decoder.codingPath.removeLast() }
guard let decoded = try self.decoder.unbox(self.container[self.currentIndex], as: UInt8.self) else {
throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath + [_JSONKey(index: self.currentIndex)], debugDescription: "Expected \(type) but found null instead."))
}
self.currentIndex += 1
return decoded
}
public mutating func decode(_ type: UInt16.Type) throws -> UInt16 {
guard !self.isAtEnd else {
throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath + [_JSONKey(index: self.currentIndex)], debugDescription: "Unkeyed container is at end."))
}
self.decoder.codingPath.append(_JSONKey(index: self.currentIndex))
defer { self.decoder.codingPath.removeLast() }
guard let decoded = try self.decoder.unbox(self.container[self.currentIndex], as: UInt16.self) else {
throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath + [_JSONKey(index: self.currentIndex)], debugDescription: "Expected \(type) but found null instead."))
}
self.currentIndex += 1
return decoded
}
public mutating func decode(_ type: UInt32.Type) throws -> UInt32 {
guard !self.isAtEnd else {
throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath + [_JSONKey(index: self.currentIndex)], debugDescription: "Unkeyed container is at end."))
}
self.decoder.codingPath.append(_JSONKey(index: self.currentIndex))
defer { self.decoder.codingPath.removeLast() }
guard let decoded = try self.decoder.unbox(self.container[self.currentIndex], as: UInt32.self) else {
throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath + [_JSONKey(index: self.currentIndex)], debugDescription: "Expected \(type) but found null instead."))
}
self.currentIndex += 1
return decoded
}
public mutating func decode(_ type: UInt64.Type) throws -> UInt64 {
guard !self.isAtEnd else {
throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath + [_JSONKey(index: self.currentIndex)], debugDescription: "Unkeyed container is at end."))
}
self.decoder.codingPath.append(_JSONKey(index: self.currentIndex))
defer { self.decoder.codingPath.removeLast() }
guard let decoded = try self.decoder.unbox(self.container[self.currentIndex], as: UInt64.self) else {
throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath + [_JSONKey(index: self.currentIndex)], debugDescription: "Expected \(type) but found null instead."))
}
self.currentIndex += 1
return decoded
}
public mutating func decode(_ type: Float.Type) throws -> Float {
guard !self.isAtEnd else {
throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath + [_JSONKey(index: self.currentIndex)], debugDescription: "Unkeyed container is at end."))
}
self.decoder.codingPath.append(_JSONKey(index: self.currentIndex))
defer { self.decoder.codingPath.removeLast() }
guard let decoded = try self.decoder.unbox(self.container[self.currentIndex], as: Float.self) else {
throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath + [_JSONKey(index: self.currentIndex)], debugDescription: "Expected \(type) but found null instead."))
}
self.currentIndex += 1
return decoded
}
public mutating func decode(_ type: Double.Type) throws -> Double {
guard !self.isAtEnd else {
throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath + [_JSONKey(index: self.currentIndex)], debugDescription: "Unkeyed container is at end."))
}
self.decoder.codingPath.append(_JSONKey(index: self.currentIndex))
defer { self.decoder.codingPath.removeLast() }
guard let decoded = try self.decoder.unbox(self.container[self.currentIndex], as: Double.self) else {
throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath + [_JSONKey(index: self.currentIndex)], debugDescription: "Expected \(type) but found null instead."))
}
self.currentIndex += 1
return decoded
}
public mutating func decode(_ type: String.Type) throws -> String {
guard !self.isAtEnd else {
throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath + [_JSONKey(index: self.currentIndex)], debugDescription: "Unkeyed container is at end."))
}
self.decoder.codingPath.append(_JSONKey(index: self.currentIndex))
defer { self.decoder.codingPath.removeLast() }
guard let decoded = try self.decoder.unbox(self.container[self.currentIndex], as: String.self) else {
throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath + [_JSONKey(index: self.currentIndex)], debugDescription: "Expected \(type) but found null instead."))
}
self.currentIndex += 1
return decoded
}
public mutating func decode<T : Decodable>(_ type: T.Type) throws -> T {
guard !self.isAtEnd else {
throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath + [_JSONKey(index: self.currentIndex)], debugDescription: "Unkeyed container is at end."))
}
self.decoder.codingPath.append(_JSONKey(index: self.currentIndex))
defer { self.decoder.codingPath.removeLast() }
guard let decoded = try self.decoder.unbox(self.container[self.currentIndex], as: type) else {
throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.decoder.codingPath + [_JSONKey(index: self.currentIndex)], debugDescription: "Expected \(type) but found null instead."))
}
self.currentIndex += 1
return decoded
}
public mutating func nestedContainer<NestedKey>(keyedBy type: NestedKey.Type) throws -> KeyedDecodingContainer<NestedKey> {
self.decoder.codingPath.append(_JSONKey(index: self.currentIndex))
defer { self.decoder.codingPath.removeLast() }
guard !self.isAtEnd else {
throw DecodingError.valueNotFound(KeyedDecodingContainer<NestedKey>.self,
DecodingError.Context(codingPath: self.codingPath,
debugDescription: "Cannot get nested keyed container -- unkeyed container is at end."))
}
let value = self.container[self.currentIndex]
guard value != .null else {
throw DecodingError.valueNotFound(KeyedDecodingContainer<NestedKey>.self,
DecodingError.Context(codingPath: self.codingPath,
debugDescription: "Cannot get keyed decoding container -- found null value instead."))
}
guard case let .dictionary(dictionary) = value else {
throw DecodingError._typeMismatch(at: self.codingPath, expectation: [String : Any].self, reality: value)
}
self.currentIndex += 1
let container = _JSONKeyedDecodingContainer<NestedKey>(referencing: self.decoder, wrapping: dictionary)
return KeyedDecodingContainer(container)
}
public mutating func nestedUnkeyedContainer() throws -> UnkeyedDecodingContainer {
self.decoder.codingPath.append(_JSONKey(index: self.currentIndex))
defer { self.decoder.codingPath.removeLast() }
guard !self.isAtEnd else {
throw DecodingError.valueNotFound(UnkeyedDecodingContainer.self,
DecodingError.Context(codingPath: self.codingPath,
debugDescription: "Cannot get nested keyed container -- unkeyed container is at end."))
}
let value = self.container[self.currentIndex]
guard value != .null else {
throw DecodingError.valueNotFound(UnkeyedDecodingContainer.self,
DecodingError.Context(codingPath: self.codingPath,
debugDescription: "Cannot get keyed decoding container -- found null value instead."))
}
guard case let .array(array) = value else {
throw DecodingError._typeMismatch(at: self.codingPath, expectation: [Any].self, reality: value)
}
self.currentIndex += 1
return _JSONUnkeyedDecodingContainer(referencing: self.decoder, wrapping: array)
}
public mutating func superDecoder() throws -> Decoder {
self.decoder.codingPath.append(_JSONKey(index: self.currentIndex))
defer { self.decoder.codingPath.removeLast() }
guard !self.isAtEnd else {
throw DecodingError.valueNotFound(Decoder.self,
DecodingError.Context(codingPath: self.codingPath,
debugDescription: "Cannot get superDecoder() -- unkeyed container is at end."))
}
let value = self.container[self.currentIndex]
self.currentIndex += 1
return _JSONDecoder(referencing: value, at: self.decoder.codingPath, options: self.decoder.options)
}
}
extension _JSONDecoder : SingleValueDecodingContainer {
// MARK: SingleValueDecodingContainer Methods
private func expectNonNull<T>(_ type: T.Type) throws {
guard !self.decodeNil() else {
throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: self.codingPath, debugDescription: "Expected \(type) but found null value instead."))
}
}
public func decodeNil() -> Bool {
return self.storage.topContainer == .null
}
public func decode(_ type: Bool.Type) throws -> Bool {
try expectNonNull(Bool.self)
return try self.unbox(self.storage.topContainer, as: Bool.self)!
}
public func decode(_ type: Int.Type) throws -> Int {
try expectNonNull(Int.self)
return try self.unbox(self.storage.topContainer, as: Int.self)!
}
public func decode(_ type: Int8.Type) throws -> Int8 {
try expectNonNull(Int8.self)
return try self.unbox(self.storage.topContainer, as: Int8.self)!
}
public func decode(_ type: Int16.Type) throws -> Int16 {
try expectNonNull(Int16.self)
return try self.unbox(self.storage.topContainer, as: Int16.self)!
}
public func decode(_ type: Int32.Type) throws -> Int32 {
try expectNonNull(Int32.self)
return try self.unbox(self.storage.topContainer, as: Int32.self)!
}
public func decode(_ type: Int64.Type) throws -> Int64 {
try expectNonNull(Int64.self)
return try self.unbox(self.storage.topContainer, as: Int64.self)!
}
public func decode(_ type: UInt.Type) throws -> UInt {
try expectNonNull(UInt.self)
return try self.unbox(self.storage.topContainer, as: UInt.self)!
}
public func decode(_ type: UInt8.Type) throws -> UInt8 {
try expectNonNull(UInt8.self)
return try self.unbox(self.storage.topContainer, as: UInt8.self)!
}
public func decode(_ type: UInt16.Type) throws -> UInt16 {
try expectNonNull(UInt16.self)
return try self.unbox(self.storage.topContainer, as: UInt16.self)!
}
public func decode(_ type: UInt32.Type) throws -> UInt32 {
try expectNonNull(UInt32.self)
return try self.unbox(self.storage.topContainer, as: UInt32.self)!
}
public func decode(_ type: UInt64.Type) throws -> UInt64 {
try expectNonNull(UInt64.self)
return try self.unbox(self.storage.topContainer, as: UInt64.self)!
}
public func decode(_ type: Float.Type) throws -> Float {
try expectNonNull(Float.self)
return try self.unbox(self.storage.topContainer, as: Float.self)!
}
public func decode(_ type: Double.Type) throws -> Double {
try expectNonNull(Double.self)
return try self.unbox(self.storage.topContainer, as: Double.self)!
}
public func decode(_ type: String.Type) throws -> String {
try expectNonNull(String.self)
return try self.unbox(self.storage.topContainer, as: String.self)!
}
public func decode<T : Decodable>(_ type: T.Type) throws -> T {
try expectNonNull(type)
return try self.unbox(self.storage.topContainer, as: type)!
}
}
// MARK: - Concrete Value Representations
extension _JSONDecoder {
/// Returns the given value unboxed from a container.
fileprivate func unbox(_ value: JSON, as type: Bool.Type) throws -> Bool? {
guard value != .null else { return nil }
switch value {
case let .bool(bool):
return bool
default:
throw DecodingError._typeMismatch(at: self.codingPath, expectation: type, reality: value)
}
}
fileprivate func unbox(_ value: JSON, as type: Int.Type) throws -> Int? {
guard value != .null else { return nil }
guard case let .number(string) = value, let number = numberFormatter.number(from: string) else {
throw DecodingError._typeMismatch(at: self.codingPath, expectation: type, reality: value)
}
let integer = number.intValue
guard NSNumber(value: integer) == number else {
throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: self.codingPath, debugDescription: "Parsed JSON number <\(number)> does not fit in \(type)."))
}
return integer
}
fileprivate func unbox(_ value: JSON, as type: Int8.Type) throws -> Int8? {
guard value != .null else { return nil }
guard case let .number(string) = value, let number = numberFormatter.number(from: string) else {
throw DecodingError._typeMismatch(at: self.codingPath, expectation: type, reality: value)
}
let integer = number.int8Value
guard NSNumber(value: integer) == number else {
throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: self.codingPath, debugDescription: "Parsed JSON number <\(number)> does not fit in \(type)."))
}
return integer
}
fileprivate func unbox(_ value: JSON, as type: Int16.Type) throws -> Int16? {
guard value != .null else { return nil }
guard case let .number(string) = value, let number = numberFormatter.number(from: string) else {
throw DecodingError._typeMismatch(at: self.codingPath, expectation: type, reality: value)
}
let integer = number.int16Value
guard NSNumber(value: integer) == number else {
throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: self.codingPath, debugDescription: "Parsed JSON number <\(number)> does not fit in \(type)."))
}
return integer
}
fileprivate func unbox(_ value: JSON, as type: Int32.Type) throws -> Int32? {
guard value != .null else { return nil }
guard case let .number(string) = value, let number = numberFormatter.number(from: string) else {
throw DecodingError._typeMismatch(at: self.codingPath, expectation: type, reality: value)
}
let integer = number.int32Value
guard NSNumber(value: integer) == number else {
throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: self.codingPath, debugDescription: "Parsed JSON number <\(number)> does not fit in \(type)."))
}
return integer
}
fileprivate func unbox(_ value: JSON, as type: Int64.Type) throws -> Int64? {
guard value != .null else { return nil }
guard case let .number(string) = value, let number = numberFormatter.number(from: string) else {
throw DecodingError._typeMismatch(at: self.codingPath, expectation: type, reality: value)
}
let integer = number.int64Value
guard NSNumber(value: integer) == number else {
throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: self.codingPath, debugDescription: "Parsed JSON number <\(number)> does not fit in \(type)."))
}
return integer
}
fileprivate func unbox(_ value: JSON, as type: UInt.Type) throws -> UInt? {
guard value != .null else { return nil }
guard case let .number(string) = value, let number = numberFormatter.number(from: string) else {
throw DecodingError._typeMismatch(at: self.codingPath, expectation: type, reality: value)
}
let integer = number.uintValue
guard NSNumber(value: integer) == number else {
throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: self.codingPath, debugDescription: "Parsed JSON number <\(number)> does not fit in \(type)."))
}
return integer
}
fileprivate func unbox(_ value: JSON, as type: UInt8.Type) throws -> UInt8? {
guard value != .null else { return nil }
guard case let .number(string) = value, let number = numberFormatter.number(from: string) else {
throw DecodingError._typeMismatch(at: self.codingPath, expectation: type, reality: value)
}
let integer = number.uint8Value
guard NSNumber(value: integer) == number else {
throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: self.codingPath, debugDescription: "Parsed JSON number <\(number)> does not fit in \(type)."))
}
return integer
}
fileprivate func unbox(_ value: JSON, as type: UInt16.Type) throws -> UInt16? {
guard value != .null else { return nil }
guard case let .number(string) = value, let number = numberFormatter.number(from: string) else {
throw DecodingError._typeMismatch(at: self.codingPath, expectation: type, reality: value)
}
let integer = number.uint16Value
guard NSNumber(value: integer) == number else {
throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: self.codingPath, debugDescription: "Parsed JSON number <\(number)> does not fit in \(type)."))
}
return integer
}
fileprivate func unbox(_ value: JSON, as type: UInt32.Type) throws -> UInt32? {
guard value != .null else { return nil }
guard case let .number(string) = value, let number = numberFormatter.number(from: string) else {
throw DecodingError._typeMismatch(at: self.codingPath, expectation: type, reality: value)
}
let integer = number.uint32Value
guard NSNumber(value: integer) == number else {
throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: self.codingPath, debugDescription: "Parsed JSON number <\(number)> does not fit in \(type)."))
}
return integer
}
fileprivate func unbox(_ value: JSON, as type: UInt64.Type) throws -> UInt64? {
guard value != .null else { return nil }
guard case let .number(string) = value, let number = numberFormatter.number(from: string) else {
throw DecodingError._typeMismatch(at: self.codingPath, expectation: type, reality: value)
}
let integer = number.uint64Value
guard NSNumber(value: integer) == number else {
throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: self.codingPath, debugDescription: "Parsed JSON number <\(number)> does not fit in \(type)."))
}
return integer
}
fileprivate func unbox(_ value: JSON, as type: Float.Type) throws -> Float? {
switch value {
case let .string(string):
if case .convertFromString(let posInfString, let negInfString, let nanString) = self.options.nonConformingFloatDecodingStrategy {
if string == posInfString {
return Float.infinity
} else if string == negInfString {
return -Float.infinity
} else if string == nanString {
return Float.nan
}
}
case let .number(number):
guard let double = Double(number), abs(double) <= Double(Float.greatestFiniteMagnitude) else {
throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: self.codingPath, debugDescription: "Parsed JSON number \(number) does not fit in \(type)."))
}
return Float(double)
default:
break
}
throw DecodingError._typeMismatch(at: self.codingPath, expectation: type, reality: value)
}
fileprivate func unbox(_ value: JSON, as type: Double.Type) throws -> Double? {
switch value {
case let .string(string):
if case .convertFromString(let posInfString, let negInfString, let nanString) = self.options.nonConformingFloatDecodingStrategy {
if string == posInfString {
return Double.infinity
} else if string == negInfString {
return -Double.infinity
} else if string == nanString {
return Double.nan
}
}
case let .number(number):
guard let double = Double(number) else {
throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: self.codingPath, debugDescription: "Parsed JSON number \(number) does not fit in \(type)."))
}
return double
default:
break
}
throw DecodingError._typeMismatch(at: self.codingPath, expectation: type, reality: value)
}
fileprivate func unbox(_ value: JSON, as type: String.Type) throws -> String? {
guard value != .null else { return nil }
guard case let .string(string) = value else {
throw DecodingError._typeMismatch(at: self.codingPath, expectation: type, reality: value)
}
return string
}
fileprivate func unbox(_ value: JSON, as type: Date.Type) throws -> Date? {
guard value != .null else { return nil }
switch self.options.dateDecodingStrategy {
case .deferredToDate:
self.storage.push(container: value)
defer { self.storage.popContainer() }
return try Date(from: self)
case .secondsSince1970:
let double = try self.unbox(value, as: Double.self)!
return Date(timeIntervalSince1970: double)
case .millisecondsSince1970:
let double = try self.unbox(value, as: Double.self)!
return Date(timeIntervalSince1970: double / 1000.0)
case .iso8601:
if #available(macOS 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *) {
let string = try self.unbox(value, as: String.self)!
guard let date = ISO8601DateFormatter().date(from: string) else {
throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: self.codingPath, debugDescription: "Expected date string to be ISO8601-formatted."))
}
return date
} else {
fatalError("ISO8601DateFormatter is unavailable on this platform.")
}
case .formatted(let formatter):
let string = try self.unbox(value, as: String.self)!
guard let date = formatter.date(from: string) else {
throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: self.codingPath, debugDescription: "Date string does not match format expected by formatter."))
}
return date
case .custom(let closure):
self.storage.push(container: value)
defer { self.storage.popContainer() }
return try closure(self)
}
}
fileprivate func unbox(_ value: JSON, as type: Data.Type) throws -> Data? {
guard value != .null else { return nil }
switch self.options.dataDecodingStrategy {
case .deferredToData:
self.storage.push(container: value)
defer { self.storage.popContainer() }
return try Data(from: self)
case .base64:
guard case let .string(string) = value else {
throw DecodingError._typeMismatch(at: self.codingPath, expectation: type, reality: value)
}
guard let data = Data(base64Encoded: string) else {
throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: self.codingPath, debugDescription: "Encountered Data is not valid Base64."))
}
return data
case .custom(let closure):
self.storage.push(container: value)
defer { self.storage.popContainer() }
return try closure(self)
}
}
fileprivate func unbox(_ value: JSON, as type: Decimal.Type) throws -> Decimal? {
guard value != .null else { return nil }
guard case let .number(number) = value else {
throw DecodingError._typeMismatch(at: self.codingPath, expectation: type, reality: value)
}
guard let decimal = Decimal(string: number) else {
throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: self.codingPath, debugDescription: "Parsed JSON number \(number) does not fit in \(type)."))
}
return decimal
}
fileprivate func unbox<T : Decodable>(_ value: JSON, as type: T.Type) throws -> T? {
if type == Date.self || type == NSDate.self {
return try self.unbox(value, as: Date.self) as? T
} else if type == Data.self || type == NSData.self {
return try self.unbox(value, as: Data.self) as? T
} else if type == URL.self || type == NSURL.self {
guard let urlString = try self.unbox(value, as: String.self) else {
return nil
}
guard let url = URL(string: urlString) else {
throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: self.codingPath,
debugDescription: "Invalid URL string."))
}
return (url as! T)
} else if type == Decimal.self || type == NSDecimalNumber.self {
return try self.unbox(value, as: Decimal.self) as? T
} else {
self.storage.push(container: value)
defer { self.storage.popContainer() }
return try type.init(from: self)
}
}
}
extension DecodingError {
fileprivate static func _typeMismatch(at path: [CodingKey], expectation: Any.Type, reality: JSON) -> DecodingError {
let description = "Expected to decode \(expectation) but found \(_typeDescription(of: reality)) instead."
return .typeMismatch(expectation, Context(codingPath: path, debugDescription: description))
}
fileprivate static func _typeDescription(of value: JSON) -> String {
switch value {
case .null:
return "a null value"
case .bool:
return "a bool"
case .number:
return "a number"
case .string:
return "a string/data"
case .array:
return "an array"
case .dictionary:
return "a dictionary"
}
}
}
fileprivate struct _JSONKey : CodingKey {
public var stringValue: String
public var intValue: Int?
public init?(stringValue: String) {
self.stringValue = stringValue
self.intValue = nil
}
public init?(intValue: Int) {
self.stringValue = "\(intValue)"
self.intValue = intValue
}
public init(stringValue: String, intValue: Int?) {
self.stringValue = stringValue
self.intValue = intValue
}
fileprivate init(index: Int) {
self.stringValue = "Index \(index)"
self.intValue = index
}
fileprivate static let `super` = _JSONKey(stringValue: "super")!
}
import Foundation
public struct JSONParser {
public enum Error: Swift.Error, Equatable {
public enum String: Swift.Error, Equatable {
case escapeSequence(index: Int)
case escapeCharacter(index: Int)
case malformedUnicode(index: Int)
case unterminated(index: Int)
}
public enum Number: Swift.Error, Equatable {
case malformed(index: Int)
case numberBeginningWithZero(index: Int)
}
public enum Bool: Swift.Error, Equatable {
case malformed(index: Int)
}
public enum Null: Swift.Error, Equatable {
case malformed(index: Int)
}
public enum Array: Swift.Error, Equatable {
case malformed(index: Int)
}
public enum Dictionary: Swift.Error, Equatable {
case malformed(index: Int)
}
case empty
case invalidCharacter(UnicodeScalar, index: Int)
case string(String)
case number(Number)
case bool(Bool)
case null(Null)
case array(Array)
case dictionary(Dictionary)
}
public static func parse(data: Data) -> Result<JSON, Error> {
guard let string = String(data: data, encoding: .utf8) else { return .failure(.empty) }
var index = 0
return parse(scalars: Array(string.unicodeScalars), index: &index)
}
internal static func parse(scalars: Array<UnicodeScalar>, index: inout Array<UnicodeScalar>.Index) -> Result<JSON, Error> {
while index < scalars.endIndex {
guard !CharacterSet.whitespacesAndNewlines.contains(scalars[index]) else {
index += 1
continue
}
switch scalars[index] {
case "{":
return parseDictionary(scalars: scalars, index: &index)
.map(JSON.dictionary)
case "[":
return parseArray(scalars: scalars, index: &index)
.map(JSON.array)
case "\"":
return parseString(scalars: scalars, index: &index)
.map(JSON.string)
.mapError(Error.string)
case "-", "0"..."9":
return parseNumber(scalars: scalars, index: &index)
.map(JSON.number)
.mapError(Error.number)
case "n":
return parseNull(scalars: scalars, index: &index)
.mapError(Error.null)
case "t", "f":
return parseBool(scalars: scalars, index: &index)
.map(JSON.bool)
.mapError(Error.bool)
default:
return .failure(.invalidCharacter(scalars[index], index: index))
}
}
return .failure(.empty)
}
internal static func parseDictionary(scalars: Array<UnicodeScalar>, index: inout Array<UnicodeScalar>.Index) -> Result<[String: JSON], Error> {
let startIndex = index
guard index < scalars.endIndex,
scalars[index] == "{"
else { return .failure(.dictionary(.malformed(index: index))) }
var elements: [String: JSON] = [:]
index += 1
while index < scalars.endIndex, scalars[index] != "}" {
switch scalars[index] {
case _ where CharacterSet.whitespacesAndNewlines.contains(scalars[index]):
index += 1
case "\"":
let key = parseString(scalars: scalars, index: &index).mapError(Error.string)
while index < scalars.endIndex, scalars[index] != ":", CharacterSet.whitespacesAndNewlines.contains(scalars[index]) {
index += 1
}
index += 1
guard index < scalars.endIndex
else { return .failure(.dictionary(.malformed(index: startIndex))) }
let value = parse(scalars: scalars, index: &index)
switch (key, value) {
case let (.success(key), .success(value)):
elements[key] = value
case let (.failure(error), _),
let (_, .failure(error)):
return .failure(error)
}
while index < scalars.endIndex, scalars[index] != "," {
switch scalars[index] {
case _ where CharacterSet.whitespacesAndNewlines.contains(scalars[index]):
index += 1
case "}":
index += 1
return .success(elements)
default:
return .failure(.dictionary(.malformed(index: index)))
}
}
index += 1
default:
return .failure(.dictionary(.malformed(index: index)))
}
}
guard index < scalars.endIndex
else { return .failure(.dictionary(.malformed(index: startIndex))) }
index += 1
return .success(elements)
}
internal static func parseArray(scalars: Array<UnicodeScalar>, index: inout Array<UnicodeScalar>.Index) -> Result<[JSON], Error> {
let startIndex = index
guard index < scalars.endIndex,
scalars[index] == "["
else { return .failure(.array(.malformed(index: startIndex))) }
var elements: [JSON] = []
index += 1
while index < scalars.endIndex, scalars[index] != "]" {
switch scalars[index] {
case ",":
return .failure(.array(.malformed(index: startIndex)))
case _ where CharacterSet.whitespacesAndNewlines.contains(scalars[index]):
index += 1
default:
switch parse(scalars: scalars, index: &index) {
case .failure(let error):
return .failure(error)
case .success(let value):
elements.append(value)
}
while index < scalars.endIndex, scalars[index] != "," {
switch scalars[index] {
case _ where CharacterSet.whitespacesAndNewlines.contains(scalars[index]):
index += 1
case "]":
index += 1
return .success(elements)
default:
return .failure(.array(.malformed(index: startIndex)))
}
}
index += 1
}
}
guard index < scalars.endIndex
else { return .failure(.array(.malformed(index: startIndex))) }
index += 1
return .success(elements)
}
internal static func parseNull(scalars: Array<UnicodeScalar>, index: inout Array<UnicodeScalar>.Index) -> Result<JSON, Error.Null> {
guard index < scalars.endIndex
else { return .failure(.malformed(index: index)) }
let literal = "null"
if scalars.dropFirst(index).prefix(literal.count) == ArraySlice(literal.unicodeScalars) {
index += literal.count
return .success(.null)
} else {
return .failure(.malformed(index: index))
}
}
internal static func parseBool(scalars: Array<UnicodeScalar>, index: inout Array<UnicodeScalar>.Index) -> Result<Bool, Error.Bool> {
guard index < scalars.endIndex
else { return .failure(.malformed(index: index)) }
let (t, f) = (true, false)
switch scalars[index] {
case "t" where scalars.dropFirst(index).prefix(t.description.count) == ArraySlice(t.description.unicodeScalars):
index += t.description.count
return .success(t)
case "f" where scalars.dropFirst(index).prefix(f.description.count) == ArraySlice(f.description.unicodeScalars):
index += f.description.count
return .success(f)
default:
return .failure(.malformed(index: index))
}
}
internal static func parseString(scalars: Array<UnicodeScalar>, index: inout Array<UnicodeScalar>.Index) -> Result<String, Error.String> {
guard index < scalars.endIndex
else { return .failure(.unterminated(index: index)) }
var string = [UnicodeScalar]()
let startIndex = index
guard scalars[index] == "\""
else { return .failure(.unterminated(index: startIndex)) }
index += 1
while index < scalars.endIndex, scalars[index] != "\"" {
switch scalars[index] {
case "\\":
guard index + 1 < scalars.endIndex else { return .failure(.escapeSequence(index: startIndex)) }
index += 1
switch scalars[index] {
case "/", "\\", "\"":
string.append(scalars[index])
case "n":
string.append("\n")
case "r":
string.append("\r")
case "t":
string.append("\t")
case "f":
string.append(.init(12))
case "b":
string.append(.init(8))
case "u":
guard index + 4 < scalars.endIndex,
let unicode = UInt32(String(scalars[(index + 1)...(index + 4)].map(Character.init)).uppercased(), radix: 16).flatMap(UnicodeScalar.init)
else { return .failure(.malformedUnicode(index: startIndex)) }
string.append(unicode)
index += 4
default:
return .failure(.escapeCharacter(index: index))
}
default:
string.append(scalars[index])
}
index += 1
}
guard index < scalars.endIndex, scalars[index] == "\""
else { return .failure(.unterminated(index: startIndex)) }
index += 1
return .success(String(string.map(Character.init)))
}
internal static func parseNumber(scalars: Array<UnicodeScalar>, index: inout Array<UnicodeScalar>.Index) -> Result<String, Error.Number> {
guard index < scalars.endIndex
else { return .failure(.malformed(index: index)) }
let transform: ([UnicodeScalar]) -> Result<String, Error.Number> = { .success(String($0.map(Character.init))) }
var number: [UnicodeScalar] = []
let startIndex = index
switch scalars[index] {
case "-":
number.append(scalars[index])
index += 1
default:
break
}
// Append all digits occurring until a non-digit is found.
var significant: [UnicodeScalar] = []
while index < scalars.endIndex, isNumeric(scalars[index]) {
significant.append(scalars[index])
index += 1
}
switch (significant.first, significant.dropFirst().first) {
case ("0"?, _?):
return .failure(.numberBeginningWithZero(index: startIndex))
default:
break
}
number.append(contentsOf: significant)
guard index < scalars.endIndex
else { return transform(number) }
switch scalars[index] {
case ".":
number.append(scalars[index])
index += 1
guard index < scalars.endIndex, isNumeric(scalars[index])
else { return .failure(.malformed(index: index)) }
while index < scalars.endIndex, isNumeric(scalars[index]) {
number.append(scalars[index])
index += 1
}
guard index < scalars.endIndex
else { return transform(number) }
default:
break
}
switch scalars[index] {
case "e", "E":
number.append(scalars[index])
index += 1
guard index < scalars.endIndex
else { return .failure(.malformed(index: startIndex)) }
switch scalars[index] {
case "-", "+":
number.append(scalars[index])
index += 1
guard index < scalars.endIndex, isNumeric(scalars[index])
else { return .failure(.malformed(index: startIndex)) }
case _ where isNumeric(scalars[index]):
break
default:
return .failure(.malformed(index: startIndex))
}
while index < scalars.endIndex, isNumeric(scalars[index]) {
number.append(scalars[index])
index += 1
}
default:
break
}
return transform(number)
}
}
internal extension JSONParser {
static func isNumeric(_ scalar: UnicodeScalar) -> Bool {
switch scalar {
case "0"..."9":
return true
default:
return false
}
}
}
//
// JSONParser.swift
// MonadicJSON macOS
//
// Created by Charlotte Tortorella on 18/4/19.
//
import XCTest
import SwiftCheck
@testable import MonadicJSON
let encoder = JSONEncoder()
// Change this `Swift.JSONDecoder` to view failures of the inbuilt decoder.
let decoder = MonadicJSONDecoder()
class JSONTests: XCTestCase {
func testParserGenerally() {
property("Random strings can't crash the parser") <- forAll { (string: String) in
_ = JSONParser.parse(string)
return true
}
}
func testStrings() {
property("Strings are all valid") <- forAll(strings) { string in
JSONParser.parseString(string).succeeded
}
property("Strings are invalid if they don't start/end with '\"'") <- forAll(invalidStrings) { string in
JSONParser.parseString(string).failed
}
property("Unicode with 4 hex digits is valid") <- forAllNoShrink(validUnicode) { (args: (scalar: UInt32, string: String)) in
JSONParser.parseString(args.string).success!.unicodeScalars.first!.value == args.scalar
}
property("Unicode can occur multiple times in a string") <- forAllNoShrink(validUnicode.map { String($1.dropFirst().dropLast()) }.proliferate) {
return JSONParser.parseString(["\"", $0.joined(), "\""].joined()).succeeded
}
property("Unicode with less than 4 hex digits is invalid") <- forAllNoShrink(Gen.zip(validUnicode.map { $1 }, .fromElements(in: (2...5))).map { [String($0.dropLast($1)), "\""].joined() }) { string in
switch JSONParser.parseString(string).failure {
case .malformedUnicode?:
return true
default:
return false
}
}
property("Unrepresentable unicode is invalid") <- forAllNoShrink(invalidUnicode) { string in
switch JSONParser.parseString(string).failure {
case .malformedUnicode?:
return true
default:
return false
}
}
property("An array of strings can be repeatedly parsed with the same index until they reach the end") <- forAllNoShrink(strings.proliferate) { strings in
var index = 0
let joined = strings.joined()
let s = Array(joined.unicodeScalars)
return
(0..<strings.count).allSatisfy { JSONParser.parseString(scalars: s, index: &index).success == strings[$0].reified } <?> "All strings parsed"
^&&^
(index == s.endIndex) <?> "index == endIndex"
}
}
func testDictionaries() {
property("Dictionaries are all valid") <- forAllNoShrink(dictionaries) { string in
return JSONParser.parse(string).succeeded
}
property("Dictionaries containing invalid strings are invalid") <- forAllNoShrink(dictionary(invalidAlphabetical, invalidAlphabetical)) { string in
JSONParser.parse(string).failed
}
property("Dictionaries that are unterminated will result in a malformed error") <- forAllNoShrink(Gen.pure("[{\"\":").proliferateNonEmpty) { strings in
switch JSONParser.parse(strings.joined()).failure {
case .dictionary(.malformed)?:
return true
default:
return false
}
}
}
func testArrays() {
property("Arrays are all valid") <- forAllNoShrink(arrays) { string in
JSONParser.parse(string).succeeded
}
property("Arrays containing invalid strings are invalid") <- forAllNoShrink(array(invalidAlphabetical)) { string in
JSONParser.parse(string).failed
}
property("Arrays can handle arbitrary nesting levels") <- forAllNoShrink(Gen.pure("[").proliferateNonEmpty.map { $0.joined() }) { string in
switch JSONParser.parse(string).failure {
case .array(.malformed)?:
return true
default:
return false
}
}
property("Unterminated arrays will result in malformed errors") <- forAllNoShrink(Gen.one(of: [strings, randomNumbers, arrays, dictionaries]).map { "[" + $0 }) { string in
switch JSONParser.parse(string).failure {
case .array(.malformed)?:
return true
default:
return false
}
}
}
func testNumbers() {
property("Numbers are all valid") <- forAll(randomNumbers) { (string: String) in
JSONParser.parse(string).success == .number(string)
}
property("Numbers that begin with 0 are invalid") <- forAll(invalidJsonNumbers) { (string: String) in
JSONParser.parse(string).failure == .number(.numberBeginningWithZero(index: 0))
}
property("NaN is invalid") <- forAll(Gen.pure("NaN").maybeCapitalized) { (string: String) in
JSONParser.parse(string).failed
}
}
func testBools() {
property("Bools are all valid") <- forAll { (bool: Bool) in
JSONParser.parse(bool.description).success == .bool(bool)
}
property("Capitalised bools are invalid") <- forAll(Bool.arbitrary.map { $0.description }.maybeCapitalized) { string in
return (![true, false].contains(where: { $0.description == string })) ==> {
JSONParser.parse(string).failed
}
}
}
func testNull() {
property("Null is valid") <- forAllNoShrink(.pure(())) { _ in
JSONParser.parse("null").succeeded
}
property("Capitalised nulls are invalid") <- forAll(Gen.pure("null").maybeCapitalized) { string in
return (string != "null") ==> {
JSONParser.parse(string).failed
}
}
}
func testDecoder() {
testOversizedInteger(type: Int.self)
testOversizedInteger(type: Int8.self)
testOversizedInteger(type: Int16.self)
testOversizedInteger(type: Int32.self)
testOversizedInteger(type: Int64.self)
testOversizedInteger(type: UInt.self)
testOversizedInteger(type: UInt8.self)
testOversizedInteger(type: UInt16.self)
testOversizedInteger(type: UInt32.self)
testOversizedInteger(type: UInt64.self)
testDecoding(for: Date.self, dateStrategy: [
(.deferredToDate, .deferredToDate),
(.secondsSince1970, .secondsSince1970),
(.millisecondsSince1970, .millisecondsSince1970),
(.iso8601, .iso8601)
]
)
testDecoding(for: Data.self, dataStrategy: [
(.deferredToData, .deferredToData),
(.base64, .base64)
]
)
testDecoding(for: Bool.self)
testDecoding(for: Float.self)
testDecoding(for: Double.self)
testDecoding(for: Int.self)
testDecoding(for: Int8.self)
testDecoding(for: Int16.self)
testDecoding(for: Int32.self)
testDecoding(for: Int64.self)
testDecoding(for: UInt.self)
testDecoding(for: UInt8.self)
testDecoding(for: UInt16.self)
testDecoding(for: UInt32.self)
testDecoding(for: UInt64.self)
testDecoding(for: Decimal.self)
testDecoding(for: String.self)
}
}
typealias CodableArbitrary = Codable & Arbitrary & Equatable
protocol Integer: Codable, Arbitrary, BinaryInteger {
static var max: Self { get }
}
extension Int: Integer {}
extension Int8: Integer {}
extension Int16: Integer {}
extension Int32: Integer {}
extension Int64: Integer {}
extension UInt: Integer {}
extension UInt8: Integer {}
extension UInt16: Integer {}
extension UInt32: Integer {}
extension UInt64: Integer {}
func testOversizedInteger<T: Integer>(type: T.Type) {
property("Oversized integer instantiation will always throw an error") <- forAll(integers.filterMap(Double.init).suchThat { $0 > Double(T.max) }) { value in
do {
_ = try decoder.decode(DecoderTest<T>.self, from: data(value))
} catch let error as DecodingError {
switch error {
case let .dataCorrupted(context):
let description = context.debugDescription
return description.hasPrefix("Parsed JSON number <") && description.hasSuffix("> does not fit in \(T.self).")
default:
break
}
}
return false
}
}
func testDecoding<T: CodableArbitrary>(for type: T.Type, dateStrategy: [(JSONEncoder.DateEncodingStrategy, MonadicJSONDecoder.DateDecodingStrategy)] = [(.deferredToDate, .deferredToDate)], dataStrategy: [(JSONEncoder.DataEncodingStrategy, MonadicJSONDecoder.DataDecodingStrategy)] = [(.deferredToData, .deferredToData)]) {
let ((dateEncoding, dateDecoding), (dataEncoding, dataDecoding)) = Gen.zip(.fromElements(of: dateStrategy), .fromElements(of: dataStrategy)).generate
encoder.dateEncodingStrategy = dateEncoding
encoder.dataEncodingStrategy = dataEncoding
decoder.dateDecodingStrategy = dateDecoding
decoder.dataDecodingStrategy = dataDecoding
_testDecoding(for: T.self)
_testDecoding(for: Optional<T>.self)
switch DecodingType.arbitrary.generate {
case .array:
_testDecoding(for: [T].self)
case .nestedArray:
_testDecoding(for: [[T]].self)
case .dictionary:
_testDecoding(for: [String: T].self)
case .nestedDictionary:
_testDecoding(for: [String: [String: T]].self)
}
}
func _testDecoding<T: CodableArbitrary>(for type: T.Type, _ gen: Gen<T> = T.arbitrary) {
property("\(T.self) is valid for all permutations") <- decodeProperty(containing: T.self, gen)
}
enum DecodingType: Arbitrary, CaseIterable {
case array
case nestedArray
case dictionary
case nestedDictionary
static var arbitrary: Gen<DecodingType> {
return .fromElements(of: allCases)
}
}
func decodeProperty<T: CodableArbitrary>(containing type: T.Type, _ gen: Gen<T>) -> Property {
print("*** Starting isomorphism tests for \(T.self) decoding")
return forAllNoShrink(gen) {
let label = "Isomorphic \(T.self) decode"
let value = try! decoder.decode(DecoderTest<T>.self, from: data($0)).value
if value == $0 {
return true <?> label
} else {
print("Value before parsing: <\($0)>, after: <\(value)>")
return false <?> label
}
}
}
func data<T: CodableArbitrary>(_ value: T) -> Data {
return try! encoder.encode(DecoderTest<T>(value: value))
}
struct DecoderTest<T: CodableArbitrary>: Equatable, Codable, Arbitrary {
let value: T
static var arbitrary: Gen<DecoderTest<T>> {
return .compose { .init(value: $0.generate()) }
}
}
extension Result {
var succeeded: Bool {
switch self {
case .success:
return true
case .failure:
return false
}
}
var failed: Bool {
return !succeeded
}
var success: Success? {
switch self {
case let .success(value):
return value
case .failure:
return nil
}
}
var failure: Failure? {
switch self {
case .success:
return nil
case let .failure(error):
return error
}
}
}
extension Data: Arbitrary {
public static var arbitrary: Gen<Data> {
return [UInt8].arbitrary.map { Data($0) }
}
}
extension Date: Arbitrary {
public static var arbitrary: Gen<Date> {
return Int.arbitrary.map(TimeInterval.init).map(Date.init(timeIntervalSince1970:))
}
}
extension Decimal: Arbitrary {
public static var arbitrary: Gen<Decimal> {
return Double.arbitrary.map { Decimal($0) }
}
}
extension Gen {
public func map<U>(_ keyPath: KeyPath<A, U>) -> Gen<U> {
return map { $0[keyPath: keyPath] }
}
public func filterMap<U>(_ transform: @escaping (A) -> U?) -> Gen<U> {
return map(transform).suchThat { $0 != nil }.map { $0! }
}
}
extension Gen where A: Sequence, A.Element == UnicodeScalar {
var string: Gen<String> {
return map { String($0.map(Character.init)) }
}
}
extension Gen where A == String {
var maybeCapitalized: Gen {
return self
.map { $0.map(String.init).map(Gen.pure).map { $0.ap(.fromElements(of: [{ $0.capitalized }, { $0 }])) } }
.flatMap(sequence)
.map { $0.joined() }
}
}
extension UnicodeScalar {
static func allScalars(from first: UnicodeScalar, upTo last: Unicode.Scalar) -> [UnicodeScalar] {
return Array(first.value ..< last.value).compactMap(UnicodeScalar.init)
}
static func allScalars(from first: UnicodeScalar, through last: UnicodeScalar) -> [UnicodeScalar] {
return allScalars(from: first, upTo: last) + [last]
}
}
extension Collection {
func intersperse(element: Element) -> [Element] {
return Array(zip(self, repeatElement(element, count: numericCast(count))).flatMap { [$0, $1] }.dropLast())
}
func intersperse(_ gen: Gen<Element>) -> [Element] {
return Array(zip(self, (0...numericCast(count)).map { _ in gen.generate }).flatMap { [$0, $1] }.dropLast())
}
func envelop(_ gen: Gen<Element>) -> [Element] {
return Array([[gen.generate], intersperse(gen), [gen.generate]].joined())
}
}
extension Collection where Element == String {
func envelop(_ set: CharacterSet) -> [String] {
let characters = Array(0..<0xFF).compactMap(UnicodeScalar.init).filter(set.contains)
let gen = Gen.fromElements(of: characters).map(Character.init)
return envelop(UInt.arbitrary.map { String(repeating: gen.generate, count: numericCast($0)) })
}
}
extension JSONParser {
static func parse(_ string: String) -> Result<JSON, JSONParser.Error> {
return JSONParser.parse(data: string.data(using: .utf8)!)
}
static func parseString(_ string: String) -> Result<String, JSONParser.Error.String> {
var index = 0
return JSONParser.parseString(scalars: Array(string.unicodeScalars), index: &index)
}
}
let allNumbers = UnicodeScalar.arbitrary.suchThat(CharacterSet(charactersIn: "0"..."9").contains).proliferate.string
let integers = Gen.one(of: [
Gen.zip(UnicodeScalar.arbitrary.suchThat(CharacterSet(charactersIn: "1"..."9").contains).map { String(Character($0)) }, .frequency([(3, allNumbers), (1, .pure(""))])).map(+),
.pure("0")
]
)
let jsonNumbers: Gen<[String]> = .compose { c in
let minus = Gen<String>.fromElements(of: ["-", ""])
let plusMinus = Gen<String>.fromElements(of: ["+", "-", ""])
let fractional = c.generate(using: Gen<String?>.fromElements(of: [".", nil]))
.map { [$0, c.generate(using: allNumbers.suchThat { !$0.isEmpty })].joined() }
?? ""
let exponential = c.generate(using: Gen<String?>.fromElements(of: ["e", "E", nil]))
.map { [$0, c.generate(using: Gen<String>.fromElements(of: ["+", "-", ""])), c.generate(using: integers)].joined() }
?? ""
return [c.generate(using: minus), c.generate(using: integers), fractional, exponential]
}
let invalidJsonNumbers: Gen<String> = jsonNumbers.map { [$0.prefix(1), ["0"], $0.dropFirst()].joined().joined() }
let randomNumbers: Gen<String> = .one(of: [
jsonNumbers.map { $0.joined() },
Double.arbitrary.map(\.description),
Int.arbitrary.map(\.description),
Float.arbitrary.map(\.description)
])
func unicode(suchThat predicate: @escaping (UInt32) -> Bool) -> Gen<(UInt32, String)> {
return Gen
.fromElements(in: 0x1000...0xFFFF)
.suchThat(predicate)
.ap(Gen.fromElements(of: ["x", "X"]).map { fmt in { ($0, String(format: "%\(fmt)", $0)) } })
.map { ($0, "\"\\u\($1)\"") }
}
let validUnicode = unicode(suchThat: { UnicodeScalar($0) != nil })
let invalidUnicode = unicode(suchThat: { UnicodeScalar($0) == nil }).map { $1 }
let strings: Gen<String> = String
.arbitrary
.map(\.encoded)
let invalidStrings: Gen<String> = strings
.ap(.fromElements(of: [{ $0.dropFirst() }, { $0.dropLast() }, { $0.dropFirst().dropLast() }]))
.map(String.init)
let invalidAlphabetical: Gen<String> = Gen
.fromElements(of: UnicodeScalar.allScalars(from: "a", through: "z") + UnicodeScalar.allScalars(from: "A", through: "Z"))
.proliferateNonEmpty
.string
.map { "\"" + $0 }
let arrays = array(Gen.one(of: [randomNumbers, strings, validUnicode.map { $1 }, dictionary(Gen.one(of: [strings, validUnicode.map { $1 }]).proliferate, Gen.one(of: [randomNumbers, strings, validUnicode.map { $1 }]).proliferate)]).proliferate)
let dictionaries = dictionary(Gen.one(of: [strings, validUnicode.map { $1 }]).proliferate, Gen.one(of: [randomNumbers, strings, validUnicode.map { $1 }, array(Gen.one(of: [randomNumbers, strings, validUnicode.map { $1 }]).proliferate)]).proliferate)
func array(_ values: Gen<[String]>) -> Gen<String> {
return values
.map { ["[", $0.intersperse(element: ",").envelop(.whitespacesAndNewlines).joined(), "]"].envelop(.whitespacesAndNewlines).joined() }
}
func array(_ values: Gen<String>) -> Gen<String> {
return values
.proliferateNonEmpty
.map { ["[", $0.intersperse(element: ",").envelop(.whitespacesAndNewlines).joined(), "]"].envelop(.whitespacesAndNewlines).joined() }
}
func dictionary(_ keys: Gen<[String]>, _ values: Gen<[String]>) -> Gen<String> {
return Gen
.zip(keys, values)
.map { ["{", zip($0, $1).map { [$0, ":", $1].envelop(.whitespacesAndNewlines).joined() }.intersperse(element: ",").envelop(.whitespacesAndNewlines).joined(), "}"].joined() }
}
func dictionary(_ keys: Gen<String>, _ values: Gen<String>) -> Gen<String> {
return Gen
.zip(keys.proliferateNonEmpty, values.proliferateNonEmpty)
.map { ["{", zip($0, $1).map { [$0, ":", $1].envelop(.whitespacesAndNewlines).joined() }.intersperse(element: ",").envelop(.whitespacesAndNewlines).joined(), "}"].joined() }
}
let escapes = [("\\", "\\\\"), ("\"", "\\\"")]
extension String {
var encoded: String {
return ["\"", escapes.reduce(self) { $0.replacingOccurrences(of: $1.0, with: $1.1) }, "\""].joined()
}
var reified: String {
return escapes.reduce(String(dropFirst().dropLast())) { $0.replacingOccurrences(of: $1.1, with: $1.0) }
}
}
extension JSON: Arbitrary {
public static var arbitraryNonRecursive: Gen<JSON> {
return .one(of: [
.pure(.null),
randomNumbers.map(JSON.number),
String.arbitrary.map(JSON.string),
Bool.arbitrary.map(JSON.bool)
]
)
}
public static var arbitrary: Gen<JSON> {
return .one(of: [
.pure(.null),
randomNumbers.map(JSON.number),
String.arbitrary.map(JSON.string),
Bool.arbitrary.map(JSON.bool),
arbitraryNonRecursive.proliferate.map(JSON.array),
Gen.zip(String.arbitrary, arbitraryNonRecursive).proliferate.map { Dictionary($0, uniquingKeysWith: { $1 }) }.map(JSON.dictionary)
]
)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment