From f8ce5d0d7623320f29f7ca80b81665a93479bb33 Mon Sep 17 00:00:00 2001 From: Kevin Ballard Date: Wed, 19 Oct 2016 22:45:56 -0700 Subject: [PATCH] Add convenience method/properties for stream decoding Add a method `values()` on `JSONStreamDecoder` that eagerly parses all values and returns it as a `[JSON]`. Also add properties `.json` and `.error` to `JSONStreamValue`. --- Sources/Decoder.swift | 27 +++++++++++++++++++++ Tests/JSONDecoderTests.swift | 47 +++++++++++++++++++++++++----------- 2 files changed, 60 insertions(+), 14 deletions(-) diff --git a/Sources/Decoder.swift b/Sources/Decoder.swift index 18521ef..0a9a0fe 100644 --- a/Sources/Decoder.swift +++ b/Sources/Decoder.swift @@ -222,6 +222,17 @@ public struct JSONStreamDecoder: Sequence, IteratorProtocol where decoder.streaming = true } + /// Returns an array of all decoded values, or throws an error if one occurs. + /// + /// This eagerly decodes the rest of the JSON stream and returns all values. If a parse error occurs at any point, + /// the error is thrown and all values are discarded. + /// + /// - Returns: An array of `JSON` values. + /// - Throws: `JSONParserError`. + public func values() throws -> [JSON] { + return try map({ try $0.unwrap() }) + } + public func makeIterator() -> JSONStreamDecoder { return self } @@ -247,6 +258,22 @@ public enum JSONStreamValue: Equatable { case json(JSON) case error(JSONParserError) + /// Returns the contained `JSON` value, otherwise `nil`. + public var json: JSON? { + switch self { + case .json(let json): return json + case .error: return nil + } + } + + /// Returns the contained error, otherwise `nil`. + public var error: JSONParserError? { + switch self { + case .json: return nil + case .error(let error): return error + } + } + /// Unwraps the contained `JSON` value or throws the contained error. /// /// - Throws: `JSONParserError`. diff --git a/Tests/JSONDecoderTests.swift b/Tests/JSONDecoderTests.swift index 7206a7c..8cfacf3 100644 --- a/Tests/JSONDecoderTests.swift +++ b/Tests/JSONDecoderTests.swift @@ -339,23 +339,42 @@ class JSONStreamDecoderTests: XCTestCase { func testStreamingDecoder() { func assertMatchesValues(_ input: String, _ expected: [JSONStreamValue], file: StaticString = #file, line: UInt = #line) { + for (i, (value, expected)) in zip(JSON.decodeStream(input), expected).enumerated() { + switch (value, expected) { + case let (.json(value), .json(expected)): + if !matchesJSON(value, expected) { + XCTFail("value \(i+1) - (\(value)) does not equal \(expected)", file: file, line: line) + } + case let (.error(error), .error(expected)): + if error != expected { + XCTFail("error \(i+1) - (\(error)) does not equal \(expected)", file: file, line: line) + } + case let (.json(value), .error(expected)): + XCTFail("value \(i+1) - expected error \(expected), found value \(value)", file: file, line: line) + case let (.error(error), .json(expected)): + XCTFail("value \(i+1) - expected value \(expected), found error \(error)", file: file, line: line) + } + } + // Also check the behavior of values() do { - for (i, (value, expected)) in zip(JSON.decodeStream(input), expected).enumerated() { - switch (value, expected) { - case let (.json(value), .json(expected)): - if !matchesJSON(value, expected) { - XCTFail("value \(i+1) - (\(value)) does not equal \(expected)", file: file, line: line) - } - case let (.error(error), .error(expected)): - if error != expected { - XCTFail("error \(i+1) - (\(error)) does not equal \(expected)", file: file, line: line) - } - case let (.json(value), .error(expected)): - XCTFail("value \(i+1) - expected error \(expected), found value \(value)") - case let (.error(error), .json(expected)): - XCTFail("value \(i+1) - expected value \(expected), found error \(error)", file: file, line: line) + let expectedValues = try expected.map({ try $0.unwrap() }) + do { + let values = try JSON.decodeStream(input).values() + XCTAssertEqual(values, expectedValues, file: file, line: line) + } catch { + XCTFail("unexpected error found decoding JSON stream - \(error)", file: file, line: line) + } + } catch let expectedError as JSONParserError { + XCTAssertThrowsError(try JSON.decodeStream(input).values()) { (error) in + switch error { + case let error as JSONParserError: + XCTAssertEqual(error, expectedError, file: file, line: line) + default: + XCTFail("expected \(expectedError), found \(error)", file: file, line: line) } } + } catch { + XCTFail(file: file, line: line) // unreachable } } func assertMatchesEvents(_ input: String, _ expected: [JSON], file: StaticString = #file, line: UInt = #line) {