From 1fae293c43f0277a9b1c590faf52d18207115669 Mon Sep 17 00:00:00 2001 From: Patrick Freed Date: Tue, 12 Jan 2021 16:15:07 -0500 Subject: [PATCH 1/2] add various extensions to JSONValue --- Sources/ExtrasJSON/JSONValue.swift | 146 ++++++++++++++++++ .../JSONValueExtensionsTests.swift | 58 +++++++ 2 files changed, 204 insertions(+) create mode 100644 Tests/ExtrasJSONTests/JSONValueExtensionsTests.swift diff --git a/Sources/ExtrasJSON/JSONValue.swift b/Sources/ExtrasJSON/JSONValue.swift index af6d851..a4b3447 100644 --- a/Sources/ExtrasJSON/JSONValue.swift +++ b/Sources/ExtrasJSON/JSONValue.swift @@ -143,6 +143,59 @@ extension JSONValue { } } +/// Value Getters +extension JSONValue { + /// If this `JSONValue` is a `.number` that can be losslessly represented as a `Double`, return it as a `Double`. + /// Otherwise, return nil. + public var doubleValue: Double? { + guard case let .number(n) = self else { + return nil + } + return Double(n) + } + + /// If this `JSONValue` is a `.number` that can be losslessly represented as an `Int`, return it as a `Int`. + /// Otherwise, return nil. + public var intValue: Int? { + guard case let.number(n) = self else { + return nil + } + return Int(n) + } + + /// If this `JSONValue` is a `.string`, return it as a `String`. Otherwise, return nil. + public var stringValue: String? { + guard case let .string(s) = self else { + return nil + } + return s + } + + /// If this `JSONValue` is a `.bool`, return it as a `Bool`. Otherwise, return nil. + public var boolValue: Bool? { + guard case let .bool(b) = self else { + return nil + } + return b + } + + /// If this `JSONValue` is a `.array`, return it as a `[JSONValue]`. Otherwise, return nil. + public var arrayValue: [JSONValue]? { + guard case let .array(a) = self else { + return nil + } + return a + } + + /// If this `JSONValue` is a `.object`, return it as a `[String: JSONValue]`. Otherwise, return nil. + public var objectValue: [String: JSONValue]? { + guard case let .object(o) = self else { + return nil + } + return o + } +} + extension JSONValue { var debugDataTypeDescription: String { switch self { @@ -217,3 +270,96 @@ public func == (lhs: JSONValue, rhs: JSONValue) -> Bool { return false } } + +extension JSONValue: ExpressibleByFloatLiteral { + public init(floatLiteral value: Double) { + self = .number(String(value)) + } +} + +extension JSONValue: ExpressibleByIntegerLiteral { + public init(integerLiteral value: Int) { + self = .number(String(value)) + } +} + +extension JSONValue: ExpressibleByStringLiteral { + public init(stringLiteral value: String) { + self = .string(value) + } +} + +extension JSONValue: ExpressibleByBooleanLiteral { + public init(booleanLiteral value: Bool) { + self = .bool(value) + } +} + +extension JSONValue: ExpressibleByArrayLiteral { + public init(arrayLiteral elements: JSONValue...) { + self = .array(elements) + } +} + +extension JSONValue: ExpressibleByDictionaryLiteral { + public init(dictionaryLiteral elements: (String, JSONValue)...) { + self = .object(Dictionary(uniqueKeysWithValues: elements)) + } +} + +extension JSONValue: Codable { + public func encode(to encoder: Encoder) throws { + var container = encoder.singleValueContainer() + switch self { + case let .number(n): + if let int = self.intValue { + try container.encode(int) + } else if let double = self.doubleValue { + try container.encode(double) + } else { + throw EncodingError.invalidValue( + self, + EncodingError.Context( + codingPath: container.codingPath, + debugDescription: "Could not encode \"\(n)\" as a number" + ) + ) + } + case let .string(s): + try container.encode(s) + case let .bool(b): + try container.encode(b) + case let .array(a): + try container.encode(a) + case let .object(o): + try container.encode(o) + case .null: + try container.encodeNil() + } + } + + public init(from decoder: Decoder) throws { + let container = try decoder.singleValueContainer() + if let int = try? container.decode(Int.self) { + self = .number(String(int)) + } else if let double = try? container.decode(Double.self) { + self = .number(String(double)) + } else if let string = try? container.decode(String.self) { + self = .string(string) + } else if let bool = try? container.decode(Bool.self) { + self = .bool(bool) + } else if let object = try? container.decode([String: JSONValue].self) { + self = .object(object) + } else if let array = try? container.decode([JSONValue].self) { + self = .array(array) + } else { + throw DecodingError.typeMismatch( + JSONValue.self, + DecodingError.Context( + codingPath: container.codingPath, + debugDescription: "No valid JSON type could be decoded from the provided input." + ) + ) + } + } +} diff --git a/Tests/ExtrasJSONTests/JSONValueExtensionsTests.swift b/Tests/ExtrasJSONTests/JSONValueExtensionsTests.swift new file mode 100644 index 0000000..e398f34 --- /dev/null +++ b/Tests/ExtrasJSONTests/JSONValueExtensionsTests.swift @@ -0,0 +1,58 @@ +import XCTest +import ExtrasJSON + +class JSONValueDecodingTests: XCTestCase { + let encoder = XJSONEncoder() + let decoder = XJSONDecoder() + + func testInteger() throws { + let intJSON: JSONValue = 12 + let encoded = try encoder.encode(intJSON) + XCTAssertEqual(Int(String(bytes: encoded, encoding: .utf8)!), intJSON.intValue) + + let decoded = try decoder.decode(JSONValue.self, from: encoded) + XCTAssertEqual(decoded.intValue, intJSON.intValue) + } + + func testDouble() throws { + let doubleJSON: JSONValue = 12.3 + let encoded = try encoder.encode(doubleJSON) + XCTAssertEqual(Double(String(bytes: encoded, encoding: .utf8)!)!, doubleJSON.doubleValue!, accuracy: 0.0001) + + let decoded = try decoder.decode(JSONValue.self, from: encoded) + XCTAssertEqual(decoded.doubleValue!, doubleJSON.doubleValue!, accuracy: 0.0001) + } + + func testString() throws { + let stringJSON: JSONValue = "I am a String" + let encoded = try encoder.encode(stringJSON) + XCTAssertEqual(String(bytes: encoded, encoding: .utf8), "\"I am a String\"") + let decoded = try decoder.decode(JSONValue.self, from: encoded) + XCTAssertEqual(decoded, stringJSON) + } + + func testBool() throws { + let boolJSON: JSONValue = true + let encoded = try encoder.encode(boolJSON) + XCTAssertEqual(String(bytes: encoded, encoding: .utf8), "true") + let decoded = try decoder.decode(JSONValue.self, from: encoded) + XCTAssertEqual(decoded, boolJSON) + } + + func testArray() throws { + let arrayJSON: JSONValue = ["I am a string in an array"] + let encoded = try encoder.encode(arrayJSON) + let decoded = try decoder.decode(JSONValue.self, from: encoded) + XCTAssertEqual(String(bytes: encoded, encoding: .utf8), "[\"I am a string in an array\"]") + XCTAssertEqual(decoded, arrayJSON) + } + + func testObject() throws { + let objectJSON: JSONValue = ["Key": "Value"] + let encoded = try encoder.encode(objectJSON) + let decoded = try decoder.decode(JSONValue.self, from: encoded) + XCTAssertEqual(String(bytes: encoded, encoding: .utf8), "{\"Key\":\"Value\"}") + XCTAssertEqual(objectJSON.objectValue!["Key"]!.stringValue!, "Value") + XCTAssertEqual(decoded, objectJSON) + } +} From 1b1c1793f87ee29a7bdd1e6a63bd47731cf47a4f Mon Sep 17 00:00:00 2001 From: Patrick Freed Date: Tue, 12 Jan 2021 16:33:01 -0500 Subject: [PATCH 2/2] swiftformat --- Sources/ExtrasJSON/JSONValue.swift | 36 +++++++++---------- .../JSONValueExtensionsTests.swift | 2 +- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/Sources/ExtrasJSON/JSONValue.swift b/Sources/ExtrasJSON/JSONValue.swift index a4b3447..7cdb99e 100644 --- a/Sources/ExtrasJSON/JSONValue.swift +++ b/Sources/ExtrasJSON/JSONValue.swift @@ -144,11 +144,11 @@ extension JSONValue { } /// Value Getters -extension JSONValue { +public extension JSONValue { /// If this `JSONValue` is a `.number` that can be losslessly represented as a `Double`, return it as a `Double`. /// Otherwise, return nil. - public var doubleValue: Double? { - guard case let .number(n) = self else { + var doubleValue: Double? { + guard case .number(let n) = self else { return nil } return Double(n) @@ -156,40 +156,40 @@ extension JSONValue { /// If this `JSONValue` is a `.number` that can be losslessly represented as an `Int`, return it as a `Int`. /// Otherwise, return nil. - public var intValue: Int? { - guard case let.number(n) = self else { + var intValue: Int? { + guard case .number(let n) = self else { return nil } return Int(n) } /// If this `JSONValue` is a `.string`, return it as a `String`. Otherwise, return nil. - public var stringValue: String? { - guard case let .string(s) = self else { + var stringValue: String? { + guard case .string(let s) = self else { return nil } return s } /// If this `JSONValue` is a `.bool`, return it as a `Bool`. Otherwise, return nil. - public var boolValue: Bool? { - guard case let .bool(b) = self else { + var boolValue: Bool? { + guard case .bool(let b) = self else { return nil } return b } /// If this `JSONValue` is a `.array`, return it as a `[JSONValue]`. Otherwise, return nil. - public var arrayValue: [JSONValue]? { - guard case let .array(a) = self else { + var arrayValue: [JSONValue]? { + guard case .array(let a) = self else { return nil } return a } /// If this `JSONValue` is a `.object`, return it as a `[String: JSONValue]`. Otherwise, return nil. - public var objectValue: [String: JSONValue]? { - guard case let .object(o) = self else { + var objectValue: [String: JSONValue]? { + guard case .object(let o) = self else { return nil } return o @@ -311,7 +311,7 @@ extension JSONValue: Codable { public func encode(to encoder: Encoder) throws { var container = encoder.singleValueContainer() switch self { - case let .number(n): + case .number(let n): if let int = self.intValue { try container.encode(int) } else if let double = self.doubleValue { @@ -325,13 +325,13 @@ extension JSONValue: Codable { ) ) } - case let .string(s): + case .string(let s): try container.encode(s) - case let .bool(b): + case .bool(let b): try container.encode(b) - case let .array(a): + case .array(let a): try container.encode(a) - case let .object(o): + case .object(let o): try container.encode(o) case .null: try container.encodeNil() diff --git a/Tests/ExtrasJSONTests/JSONValueExtensionsTests.swift b/Tests/ExtrasJSONTests/JSONValueExtensionsTests.swift index e398f34..4200db9 100644 --- a/Tests/ExtrasJSONTests/JSONValueExtensionsTests.swift +++ b/Tests/ExtrasJSONTests/JSONValueExtensionsTests.swift @@ -1,5 +1,5 @@ -import XCTest import ExtrasJSON +import XCTest class JSONValueDecodingTests: XCTestCase { let encoder = XJSONEncoder()