From 843d78ed2bc7f0a808d381e54b838076b39b81c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Minh=20Nguye=CC=82=CC=83n?= Date: Wed, 29 Sep 2021 17:04:46 -0700 Subject: [PATCH] Streamlined conversions to JSON types --- Sources/Turf/JSON.swift | 92 ++++++++++++++++++++++++--------- Tests/TurfTests/JSONTests.swift | 18 +++++++ 2 files changed, 86 insertions(+), 24 deletions(-) diff --git a/Sources/Turf/JSON.swift b/Sources/Turf/JSON.swift index bf6cbe8c..4ca43f82 100644 --- a/Sources/Turf/JSON.swift +++ b/Sources/Turf/JSON.swift @@ -27,9 +27,9 @@ public enum JSONValue: Equatable { /// An object containing JSON values and `null` values keyed by strings. case object(_ properties: JSONObject) - /// Initializes a JSON value representing the given string. - public init(_ string: String) { - self = .string(string) + /// Initializes a JSON value representing the given JSON value–convertible instance. + public init(_ value: JSONValueConvertible) { + self = value.jsonValue } /** @@ -38,27 +38,22 @@ public enum JSONValue: Equatable { - parameter number: An integer. JSON does not distinguish numeric types of different precisions, so the integer is stored as a floating-point number. */ public init(_ number: Source) where Source: BinaryInteger { - self = .number(Double(number)) + self.init(Double(number)) } /// Initializes a JSON value representing the given floating-point number. public init(_ number: Source) where Source: BinaryFloatingPoint { - self = .number(Double(number)) + self.init(Double(number)) } - /// Initializes a JSON value representing the given Boolean value. - public init(_ bool: Bool) { - self = .boolean(bool) + /// Initializes a JSON value representing the given JSON-convertible array. + public init(_ values: [JSONValueConvertible?]) { + self = .array(JSONArray(values)) } - /// Initializes a JSON value representing the given JSON array. - public init(_ values: JSONArray) { - self = .array(values) - } - - /// Initializes a JSON value representing the given JSON object. - public init(_ properties: JSONObject) { - self = .object(properties) + /// Initializes a JSON value representing the given JSON-convertible object. + public init(_ properties: [String: JSONValueConvertible?]) { + self = .object(JSONObject(properties)) } } @@ -105,6 +100,13 @@ extension JSONValue: RawRepresentable { */ public typealias JSONArray = [JSONValue?] +extension JSONArray { + public init(_ elements: [JSONValueConvertible?]) { + let values = elements.map { $0?.jsonValue } + self.init(values) + } +} + extension JSONArray: RawRepresentable { public typealias RawValue = [Any?] @@ -122,6 +124,12 @@ extension JSONArray: RawRepresentable { */ public typealias JSONObject = [String: JSONValue?] +extension JSONObject { + public init(_ elements: [String: JSONValueConvertible?]) { + self.init(uniqueKeysWithValues: elements.map { ($0.0, $0.1?.jsonValue) }) + } +} + extension JSONObject: RawRepresentable { public typealias RawValue = [String: Any?] @@ -136,42 +144,42 @@ extension JSONObject: RawRepresentable { extension JSONValue: ExpressibleByStringLiteral { public init(stringLiteral value: StringLiteralType) { - self = .init(value) + self.init(value) } } extension JSONValue: ExpressibleByIntegerLiteral { public init(integerLiteral value: IntegerLiteralType) { - self = .init(value) + self.init(value) } } extension JSONValue: ExpressibleByFloatLiteral { public init(floatLiteral value: FloatLiteralType) { - self = .init(value) + self.init(value) } } extension JSONValue: ExpressibleByBooleanLiteral { public init(booleanLiteral value: BooleanLiteralType) { - self = .init(value) + self.init(value) } } extension JSONValue: ExpressibleByArrayLiteral { - public typealias ArrayLiteralElement = JSONValue? + public typealias ArrayLiteralElement = JSONValueConvertible? public init(arrayLiteral elements: ArrayLiteralElement...) { - self = .init(elements) + self.init(elements) } } extension JSONValue: ExpressibleByDictionaryLiteral { public typealias Key = String - public typealias Value = JSONValue? + public typealias Value = JSONValueConvertible? public init(dictionaryLiteral elements: (Key, Value)...) { - self = .init(.init(uniqueKeysWithValues: elements)) + self = .object(JSONObject(uniqueKeysWithValues: elements.map { ($0.0, $0.1?.jsonValue) })) } } @@ -209,3 +217,39 @@ extension JSONValue: Codable { } } } + +/** + A type that can be represented as a `JSONValue` instance. + */ +public protocol JSONValueConvertible { + /// The instance wrapped in a `JSONValue` instance. + var jsonValue: JSONValue { get } +} + +extension String: JSONValueConvertible { + public var jsonValue: JSONValue { return .string(self) } +} + +extension Int: JSONValueConvertible { + public var jsonValue: JSONValue { return .number(Double(self)) } +} + +extension Double: JSONValueConvertible { + public var jsonValue: JSONValue { return .number(Double(self)) } +} + +extension Bool: JSONValueConvertible { + public var jsonValue: JSONValue { return .boolean(self) } +} + +extension Array: JSONValueConvertible where Element == JSONValueConvertible? { + public var jsonValue: JSONValue { + return .array(map { $0?.jsonValue }) + } +} + +extension Dictionary: JSONValueConvertible where Key == String, Value == JSONValueConvertible? { + public var jsonValue: JSONValue { + return .object(JSONObject(uniqueKeysWithValues: map { ($0.0, $0.1?.jsonValue) })) + } +} diff --git a/Tests/TurfTests/JSONTests.swift b/Tests/TurfTests/JSONTests.swift index fc4d946c..c48dd726 100644 --- a/Tests/TurfTests/JSONTests.swift +++ b/Tests/TurfTests/JSONTests.swift @@ -44,6 +44,24 @@ class JSONTests: XCTestCase { XCTAssertEqual(JSONObject(rawValue: ["set": Set(["Get"])]), ["set": nil]) } + func testConversion() { + XCTAssertEqual(JSONValue(String("Jason")), .string("Jason")) + XCTAssertEqual(JSONValue(Int32.max), .number(Double(Int32.max))) + XCTAssertEqual(JSONValue(Float(0.0).nextUp), .number(Double(Float(0.0).nextUp))) + XCTAssertEqual(JSONValue(0.0.nextUp), .number(0.0.nextUp)) + XCTAssertEqual(JSONValue(Bool(true)), .boolean(true)) + XCTAssertEqual(JSONValue(Bool(false)), .boolean(false)) + + let array = "Jason".map(String.init) + [nil] + XCTAssertEqual(JSONValue(array), .array(["J", "a", "s", "o", "n", nil])) + let dictionary = ["string": "Jason", "null": nil] + XCTAssertEqual(JSONValue(dictionary), .object(["string": "Jason", "null": nil])) + + XCTAssertEqual(JSONArray(Array(Set(["Get"]))), ["Get"]) + XCTAssertEqual(JSONArray(array), ["J", "a", "s", "o", "n", nil]) + XCTAssertEqual(JSONObject(dictionary), ["string": "Jason", "null": nil]) + } + func testLiterals() throws { if case let JSONValue.string(string) = "Jason" { XCTAssertEqual(string, "Jason")