Skip to content

Commit

Permalink
Streamlined conversions to JSON types
Browse files Browse the repository at this point in the history
  • Loading branch information
1ec5 committed Oct 6, 2021
1 parent 33da423 commit 843d78e
Show file tree
Hide file tree
Showing 2 changed files with 86 additions and 24 deletions.
92 changes: 68 additions & 24 deletions Sources/Turf/JSON.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

/**
Expand All @@ -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<Source>(_ 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<Source>(_ 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))
}
}

Expand Down Expand Up @@ -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?]

Expand All @@ -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?]

Expand All @@ -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) }))
}
}

Expand Down Expand Up @@ -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) }))
}
}
18 changes: 18 additions & 0 deletions Tests/TurfTests/JSONTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down

0 comments on commit 843d78e

Please sign in to comment.