Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Streamline conversions to JSON types #164

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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("Jason".map(\.description)), ["J", "a", "s", "o", "n"])
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