Skip to content

Commit

Permalink
Add method JSON.parser(for:options:)
Browse files Browse the repository at this point in the history
This method takes a `Data` and returns a
`JSONParser<AnySequence<UnicodeScalar>>`.
  • Loading branch information
Kevin Ballard committed Jan 19, 2017
1 parent b387d7e commit c21e8b9
Show file tree
Hide file tree
Showing 3 changed files with 64 additions and 1 deletion.
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,10 @@ Unless you explicitly state otherwise, any contribution intentionally submitted

## Version History

#### Development

* Add method `JSON.parser(for:options:)` that returns a `JSONParser<AnySequence<UnicodeScalar>>` from a `Data`. Like `JSON.decode(_:options:)`, this method automatically detects UTF-8, UTF-16, or UTF-32 input.

#### v2.0.0 (2017-01-02)

* Add full support for decimal numbers (on supported platforms). This takes the form of a new `JSON` variant `.decimal`, any relevant accessors, and full parsing/decoding support with the new option `.useDecimals`. With this option, any number that would have been decoded as a `Double` will be decoded as a `Decimal` instead.
Expand Down
19 changes: 18 additions & 1 deletion Sources/ObjectiveC.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
/// Decodes a `Data` as JSON.
/// - Note: Invalid unicode sequences in the data are replaced with U+FFFD.
/// - Parameter data: The data to decode. Must be UTF-8, UTF-16, or UTF-32, and may start with a BOM.
/// - Parameter options: Options that controls JSON parsing. Defaults to no options. See `JSONOptions` for details.
/// - Parameter options: Options that control JSON parsing. Defaults to no options. See `JSONOptions` for details.
/// - Returns: A `JSON` value.
/// - Throws: `JSONParserError` if the data does not contain valid JSON.
public static func decode(_ data: Data, options: JSONOptions = []) throws -> JSON {
Expand Down Expand Up @@ -66,6 +66,23 @@
}
}

extension JSON {
/// Returns a `JSONParser` that parses the given `Data` as JSON.
/// - Note: Invalid unicode sequences in the data are replaced with U+FFFD.
/// - Parameter data: The data to parse. Must be UTF-8, UTF-16, or UTF-32, and may start with a BOM.
/// - Parameter options: Options that control JSON parsing. Defaults to no options. See `JSONParserOptions` for details.
/// - Returns: A `JSONParser` value.
public static func parser(for data: Data, options: JSONParserOptions = []) -> JSONParser<AnySequence<UnicodeScalar>> {
if let endian = UTF32Decoder.encodes(data) {
return JSONParser(AnySequence(UTF32Decoder(data: data, endian: endian)), options: options)
} else if let endian = UTF16Decoder.encodes(data) {
return JSONParser(AnySequence(UTF16Decoder(data: data, endian: endian)), options: options)
} else {
return JSONParser(AnySequence(UTF8Decoder(data: data)), options: options)
}
}
}

extension JSON {
/// Converts a JSON-compatible Foundation object into a `JSON` value.
/// - Throws: `JSONFoundationError` if the object is not JSON-compatible.
Expand Down
42 changes: 42 additions & 0 deletions Tests/JSONParserTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -54,10 +54,52 @@ class JSONParserTests: XCTestCase {
XCTFail()
}
}

func testUTF32Parse() {
let input = "{ \"msg\": \"안녕하세요\" }"
block: do {
guard let data = input.data(using: .utf32) else { XCTFail("Could not get UTF-32 data"); break block }
assertParserEvents(JSON.parser(for: data), [.objectStart, .stringValue("msg"), .stringValue("안녕하세요"), .objectEnd])
}
block: do {
guard let data = input.data(using: .utf32BigEndian) else { XCTFail("Could not get UTF-32BE data"); break block }
assertParserEvents(JSON.parser(for: data), [.objectStart, .stringValue("msg"), .stringValue("안녕하세요"), .objectEnd])
}
block: do {
guard let data = input.data(using: .utf32LittleEndian) else { XCTFail("Could not get UTF-32LE data"); break block }
assertParserEvents(JSON.parser(for: data), [.objectStart, .stringValue("msg"), .stringValue("안녕하세요"), .objectEnd])
}
}

func testUTF16Parse() {
let input = "{ \"msg\": \"안녕하세요\" }"
block: do {
guard let data = input.data(using: .utf16) else { XCTFail("Could not get UTF-16 data"); break block }
assertParserEvents(JSON.parser(for: data), [.objectStart, .stringValue("msg"), .stringValue("안녕하세요"), .objectEnd])
}
block: do {
guard let data = input.data(using: .utf16BigEndian) else { XCTFail("Could not get UTF-16BE data"); break block }
assertParserEvents(JSON.parser(for: data), [.objectStart, .stringValue("msg"), .stringValue("안녕하세요"), .objectEnd])
}
block: do {
guard let data = input.data(using: .utf16LittleEndian) else { XCTFail("Could not get UTF-16LE data"); break block }
assertParserEvents(JSON.parser(for: data), [.objectStart, .stringValue("msg"), .stringValue("안녕하세요"), .objectEnd])
}
}

func testUTF8Parse() {
let input = "{ \"msg\": \"안녕하세요\" }"
guard let data = input.data(using: .utf8) else { return XCTFail("Could not get UTF-8 data") }
assertParserEvents(JSON.parser(for: data), [.objectStart, .stringValue("msg"), .stringValue("안녕하세요"), .objectEnd])
}
}

private func assertParserEvents(_ input: String, streaming: Bool = false, _ events: [JSONEvent], file: StaticString = #file, line: UInt = #line) {
let parser = JSONParser(input.unicodeScalars, options: JSONParserOptions(streaming: streaming))
assertParserEvents(parser, events, file: file, line: line)
}

private func assertParserEvents<Seq: Sequence>(_ parser: JSONParser<Seq>, _ events: [JSONEvent], file: StaticString = #file, line: UInt = #line) where Seq.Iterator.Element == UnicodeScalar {
var iter = parser.makeIterator()
for (i, expected) in events.enumerated() {
guard let event = iter.next() else {
Expand Down

0 comments on commit c21e8b9

Please sign in to comment.