Permalink
Switch branches/tags
swift-DEVELOPMENT-SNAPSHOT-2018-11-14-a swift-DEVELOPMENT-SNAPSHOT-2018-11-13-a swift-DEVELOPMENT-SNAPSHOT-2018-11-01-a swift-DEVELOPMENT-SNAPSHOT-2018-10-24-a swift-DEVELOPMENT-SNAPSHOT-2018-10-23-a swift-DEVELOPMENT-SNAPSHOT-2018-10-22-a swift-DEVELOPMENT-SNAPSHOT-2018-10-21-a swift-DEVELOPMENT-SNAPSHOT-2018-10-20-a swift-DEVELOPMENT-SNAPSHOT-2018-10-19-a swift-DEVELOPMENT-SNAPSHOT-2018-10-03-a swift-DEVELOPMENT-SNAPSHOT-2018-10-02-a swift-DEVELOPMENT-SNAPSHOT-2018-10-01-a swift-DEVELOPMENT-SNAPSHOT-2018-09-28-a swift-DEVELOPMENT-SNAPSHOT-2018-09-27-a swift-DEVELOPMENT-SNAPSHOT-2018-09-22-a swift-DEVELOPMENT-SNAPSHOT-2018-09-21-a swift-DEVELOPMENT-SNAPSHOT-2018-09-20-a swift-DEVELOPMENT-SNAPSHOT-2018-09-19-a swift-DEVELOPMENT-SNAPSHOT-2018-09-18-a swift-DEVELOPMENT-SNAPSHOT-2018-09-14-a swift-DEVELOPMENT-SNAPSHOT-2018-09-13-a swift-DEVELOPMENT-SNAPSHOT-2018-09-10-a swift-DEVELOPMENT-SNAPSHOT-2018-09-08-a swift-DEVELOPMENT-SNAPSHOT-2018-09-07-a swift-DEVELOPMENT-SNAPSHOT-2018-09-04-a swift-DEVELOPMENT-SNAPSHOT-2018-08-25-a swift-DEVELOPMENT-SNAPSHOT-2018-08-24-a swift-DEVELOPMENT-SNAPSHOT-2018-08-23-a swift-DEVELOPMENT-SNAPSHOT-2018-08-22-a swift-DEVELOPMENT-SNAPSHOT-2018-08-21-a swift-DEVELOPMENT-SNAPSHOT-2018-08-20-a swift-DEVELOPMENT-SNAPSHOT-2018-08-18-a swift-DEVELOPMENT-SNAPSHOT-2018-08-16-a swift-DEVELOPMENT-SNAPSHOT-2018-08-15-a swift-DEVELOPMENT-SNAPSHOT-2018-08-14-a swift-DEVELOPMENT-SNAPSHOT-2018-08-10-a swift-DEVELOPMENT-SNAPSHOT-2018-08-09-a swift-DEVELOPMENT-SNAPSHOT-2018-08-06-a swift-DEVELOPMENT-SNAPSHOT-2018-08-02-a swift-DEVELOPMENT-SNAPSHOT-2018-08-01-a swift-DEVELOPMENT-SNAPSHOT-2018-07-31-a swift-DEVELOPMENT-SNAPSHOT-2018-07-30-a swift-DEVELOPMENT-SNAPSHOT-2018-07-28-a swift-DEVELOPMENT-SNAPSHOT-2018-07-27-a swift-DEVELOPMENT-SNAPSHOT-2018-07-24-a swift-DEVELOPMENT-SNAPSHOT-2018-07-23-a swift-DEVELOPMENT-SNAPSHOT-2018-07-22-a swift-DEVELOPMENT-SNAPSHOT-2018-07-21-a swift-DEVELOPMENT-SNAPSHOT-2018-07-20-a swift-DEVELOPMENT-SNAPSHOT-2018-07-19-a swift-DEVELOPMENT-SNAPSHOT-2018-07-18-a swift-DEVELOPMENT-SNAPSHOT-2018-07-17-a swift-DEVELOPMENT-SNAPSHOT-2018-07-16-a swift-DEVELOPMENT-SNAPSHOT-2018-07-14-a swift-DEVELOPMENT-SNAPSHOT-2018-07-13-a swift-DEVELOPMENT-SNAPSHOT-2018-07-12-a swift-DEVELOPMENT-SNAPSHOT-2018-07-11-a swift-DEVELOPMENT-SNAPSHOT-2018-07-09-a swift-DEVELOPMENT-SNAPSHOT-2018-07-07-a swift-DEVELOPMENT-SNAPSHOT-2018-07-06-a swift-DEVELOPMENT-SNAPSHOT-2018-07-05-a swift-DEVELOPMENT-SNAPSHOT-2018-07-04-a swift-DEVELOPMENT-SNAPSHOT-2018-07-03-a swift-DEVELOPMENT-SNAPSHOT-2018-07-02-a swift-DEVELOPMENT-SNAPSHOT-2018-07-01-a swift-DEVELOPMENT-SNAPSHOT-2018-06-30-a swift-DEVELOPMENT-SNAPSHOT-2018-06-29-a swift-DEVELOPMENT-SNAPSHOT-2018-06-27-a swift-DEVELOPMENT-SNAPSHOT-2018-06-26-a swift-DEVELOPMENT-SNAPSHOT-2018-06-25-a swift-DEVELOPMENT-SNAPSHOT-2018-06-24-a swift-DEVELOPMENT-SNAPSHOT-2018-06-23-a swift-DEVELOPMENT-SNAPSHOT-2018-06-22-a swift-DEVELOPMENT-SNAPSHOT-2018-06-21-a swift-DEVELOPMENT-SNAPSHOT-2018-06-20-a swift-DEVELOPMENT-SNAPSHOT-2018-06-19-a swift-DEVELOPMENT-SNAPSHOT-2018-06-18-a swift-DEVELOPMENT-SNAPSHOT-2018-06-17-a swift-DEVELOPMENT-SNAPSHOT-2018-06-16-a swift-DEVELOPMENT-SNAPSHOT-2018-06-15-a swift-DEVELOPMENT-SNAPSHOT-2018-06-14-a swift-DEVELOPMENT-SNAPSHOT-2018-06-08-a swift-DEVELOPMENT-SNAPSHOT-2018-06-07-a swift-DEVELOPMENT-SNAPSHOT-2018-06-06-a swift-DEVELOPMENT-SNAPSHOT-2018-06-05-a swift-DEVELOPMENT-SNAPSHOT-2018-06-04-a swift-DEVELOPMENT-SNAPSHOT-2018-06-03-a swift-DEVELOPMENT-SNAPSHOT-2018-06-02-a swift-DEVELOPMENT-SNAPSHOT-2018-06-01-a swift-DEVELOPMENT-SNAPSHOT-2018-05-31-a swift-DEVELOPMENT-SNAPSHOT-2018-05-30-a swift-DEVELOPMENT-SNAPSHOT-2018-05-29-a swift-DEVELOPMENT-SNAPSHOT-2018-05-28-a swift-DEVELOPMENT-SNAPSHOT-2018-05-27-a swift-DEVELOPMENT-SNAPSHOT-2018-05-26-a swift-DEVELOPMENT-SNAPSHOT-2018-05-25-a swift-DEVELOPMENT-SNAPSHOT-2018-05-24-a swift-DEVELOPMENT-SNAPSHOT-2018-05-23-a swift-DEVELOPMENT-SNAPSHOT-2018-05-22-a swift-DEVELOPMENT-SNAPSHOT-2018-05-21-a
Nothing to show
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
344 lines (296 sloc) 11.1 KB
/*
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 Swift project authors
-------------------------------------------------------------------------
This file defines JSON support infrastructure. It is not designed to be general
purpose JSON utilities, but rather just the infrastructure which SwiftPM needs
to manage serialization of data through JSON.
*/
// MARK: JSON Item Definition
/// A JSON value.
///
/// This type uses container wrappers in order to allow for mutable elements.
public enum JSON {
/// The null value.
case null
/// A boolean value.
case bool(Bool)
/// An integer value.
///
/// While not strictly present in JSON, we use this as a convenience to
/// parsing code.
case int(Int)
/// A floating-point value.
case double(Double)
/// A string.
case string(String)
/// An array.
case array([JSON])
/// A dictionary.
case dictionary([String: JSON])
/// An ordered dictionary.
case orderedDictionary(DictionaryLiteral<String, JSON>)
}
/// A JSON representation of an element.
public protocol JSONSerializable {
/// Return a JSON representation.
func toJSON() -> JSON
}
extension JSON: CustomStringConvertible {
public var description: Swift.String {
switch self {
case .null: return "null"
case .bool(let value): return value.description
case .int(let value): return value.description
case .double(let value): return value.description
case .string(let value): return value.debugDescription
case .array(let values): return values.description
case .dictionary(let values): return values.description
case .orderedDictionary(let values): return values.description
}
}
}
/// Equatable conformance.
extension JSON: Equatable {
public static func == (lhs: JSON, rhs: JSON) -> Bool {
switch (lhs, rhs) {
case (.null, .null): return true
case (.null, _): return false
case (.bool(let a), .bool(let b)): return a == b
case (.bool, _): return false
case (.int(let a), .int(let b)): return a == b
case (.int, _): return false
case (.double(let a), .double(let b)): return a == b
case (.double, _): return false
case (.string(let a), .string(let b)): return a == b
case (.string, _): return false
case (.array(let a), .array(let b)): return a == b
case (.array, _): return false
case (.dictionary(let a), .dictionary(let b)): return a == b
case (.dictionary, _): return false
case (.orderedDictionary(let a), .orderedDictionary(let b)): return a == b
case (.orderedDictionary, _): return false
}
}
}
// MARK: JSON Encoding
extension JSON {
/// Encode a JSON item into a string of bytes.
public func toBytes(prettyPrint: Bool = false) -> ByteString {
let stream = BufferedOutputByteStream()
write(to: stream, indent: prettyPrint ? 0 : nil)
if prettyPrint {
stream.write("\n")
}
return stream.bytes
}
/// Encode a JSON item into a JSON string
public func toString(prettyPrint: Bool = false) -> String {
guard let contents = self.toBytes(prettyPrint: prettyPrint).asString else {
fatalError("Failed to serialize JSON: \(self)")
}
return contents
}
}
/// Support writing to a byte stream.
extension JSON: ByteStreamable {
public func write(to stream: OutputByteStream) {
write(to: stream, indent: nil)
}
public func write(to stream: OutputByteStream, indent: Int?) {
func indentStreamable(offset: Int? = nil) -> ByteStreamable {
return Format.asRepeating(string: " ", count: indent.flatMap({ $0 + (offset ?? 0) }) ?? 0)
}
let shouldIndent = indent != nil
switch self {
case .null:
stream <<< "null"
case .bool(let value):
stream <<< Format.asJSON(value)
case .int(let value):
stream <<< Format.asJSON(value)
case .double(let value):
// FIXME: What happens for NaN, etc.?
stream <<< Format.asJSON(value)
case .string(let value):
stream <<< Format.asJSON(value)
case .array(let contents):
stream <<< "[" <<< (shouldIndent ? "\n" : "")
for (i, item) in contents.enumerated() {
if i != 0 { stream <<< "," <<< (shouldIndent ? "\n" : " ") }
stream <<< indentStreamable(offset: 2)
item.write(to: stream, indent: indent.flatMap({ $0 + 2 }))
}
stream <<< (shouldIndent ? "\n" : "") <<< indentStreamable() <<< "]"
case .dictionary(let contents):
// We always output in a deterministic order.
stream <<< "{" <<< (shouldIndent ? "\n" : "")
for (i, key) in contents.keys.sorted().enumerated() {
if i != 0 { stream <<< "," <<< (shouldIndent ? "\n" : " ") }
stream <<< indentStreamable(offset: 2) <<< Format.asJSON(key) <<< ": "
contents[key]!.write(to: stream, indent: indent.flatMap({ $0 + 2 }))
}
stream <<< (shouldIndent ? "\n" : "") <<< indentStreamable() <<< "}"
case .orderedDictionary(let contents):
stream <<< "{" <<< (shouldIndent ? "\n" : "")
for (i, item) in contents.enumerated() {
if i != 0 { stream <<< "," <<< (shouldIndent ? "\n" : " ") }
stream <<< indentStreamable(offset: 2) <<< Format.asJSON(item.key) <<< ": "
item.value.write(to: stream, indent: indent.flatMap({ $0 + 2 }))
}
stream <<< (shouldIndent ? "\n" : "") <<< indentStreamable() <<< "}"
}
}
}
// MARK: JSON Decoding
import Foundation
enum JSONDecodingError: Swift.Error {
/// The input byte string is malformed.
case malformed
}
// NOTE: This implementation is carefully crafted to work correctly on both
// Linux and OS X while still compiling for both. Thus, the implementation takes
// Any even though it could take AnyObject on OS X, and it uses converts to
// direct Swift types (for Linux) even though those don't apply on OS X.
//
// This allows the code to be portable, and expose a portable API, but it is not
// very efficient.
private let nsBooleanType = type(of: NSNumber(value: false))
extension JSON {
private static func convertToJSON(_ object: Any) -> JSON {
switch object {
case is NSNull:
return .null
case let value as String:
return .string(value)
case let value as NSNumber:
// Check if this is a boolean.
//
// FIXME: This is all rather unfortunate and expensive.
if type(of: value) === nsBooleanType {
return .bool(value != 0)
}
// Check if this is an exact integer.
//
// FIXME: This is highly questionable. Aside from the performance of
// decoding in this fashion, it means clients which truly have
// arrays of real numbers will need to be prepared to see either an
// .int or a .double. However, for our specific use case we usually
// want to get integers out of JSON, and so it seems an ok tradeoff
// versus forcing all clients to cast out of a double.
let asInt = value.intValue
if NSNumber(value: asInt) == value {
return .int(asInt)
}
// Otherwise, we have a floating point number.
return .double(value.doubleValue)
case let value as NSArray:
return .array(value.map(convertToJSON))
case let value as NSDictionary:
var result = [String: JSON]()
for (key, val) in value {
result[key as! String] = convertToJSON(val)
}
return .dictionary(result)
// On Linux, the JSON deserialization handles this.
case let asBool as Bool: // This is true on Linux.
return .bool(asBool)
case let asInt as Int: // This is true on Linux.
return .int(asInt)
case let asDouble as Double: // This is true on Linux.
return .double(asDouble)
case let value as [Any]:
return .array(value.map(convertToJSON))
case let value as [String: Any]:
var result = [String: JSON]()
for (key, val) in value {
result[key] = convertToJSON(val)
}
return .dictionary(result)
default:
fatalError("unexpected object: \(object) \(type(of: object))")
}
}
/// Load a JSON item from a Data object.
public init(data: Data) throws {
do {
let result = try JSONSerialization.jsonObject(with: data, options: [.allowFragments])
// Convert to a native representation.
//
// FIXME: This is inefficient; eventually, we want a way to do the
// loading and not need to copy / traverse all of the data multiple
// times.
self = JSON.convertToJSON(result)
} catch {
throw JSONDecodingError.malformed
}
}
/// Load a JSON item from a byte string.
public init(bytes: ByteString) throws {
try self.init(data: Data(bytes: bytes.contents))
}
/// Convenience initalizer for UTF8 encoded strings.
///
/// - Throws: JSONDecodingError
public init(string: String) throws {
let bytes = ByteString(encodingAsUTF8: string)
try self.init(bytes: bytes)
}
}
// MARK: - JSONSerializable helpers.
extension JSON {
public init(_ dict: [String: JSONSerializable]) {
self = .dictionary(Dictionary(items: dict.map({ ($0.0, $0.1.toJSON()) })))
}
}
extension Int: JSONSerializable {
public func toJSON() -> JSON {
return .int(self)
}
}
extension Double: JSONSerializable {
public func toJSON() -> JSON {
return .double(self)
}
}
extension String: JSONSerializable {
public func toJSON() -> JSON {
return .string(self)
}
}
extension Bool: JSONSerializable {
public func toJSON() -> JSON {
return .bool(self)
}
}
extension AbsolutePath: JSONSerializable {
public func toJSON() -> JSON {
return .string(asString)
}
}
extension RelativePath: JSONSerializable {
public func toJSON() -> JSON {
return .string(asString)
}
}
extension Optional where Wrapped: JSONSerializable {
public func toJSON() -> JSON {
switch self {
case .some(let wrapped): return wrapped.toJSON()
case .none: return .null
}
}
}
extension Sequence where Iterator.Element: JSONSerializable {
public func toJSON() -> JSON {
return .array(map({ $0.toJSON() }))
}
}
extension JSON: JSONSerializable {
public func toJSON() -> JSON {
return self
}
}