Skip to content

Commit c21e8b9

Browse files
author
Kevin Ballard
committed
Add method JSON.parser(for:options:)
This method takes a `Data` and returns a `JSONParser<AnySequence<UnicodeScalar>>`.
1 parent b387d7e commit c21e8b9

3 files changed

Lines changed: 64 additions & 1 deletion

File tree

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,10 @@ Unless you explicitly state otherwise, any contribution intentionally submitted
248248

249249
## Version History
250250

251+
#### Development
252+
253+
* 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.
254+
251255
#### v2.0.0 (2017-01-02)
252256

253257
* 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.

Sources/ObjectiveC.swift

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
/// Decodes a `Data` as JSON.
2121
/// - Note: Invalid unicode sequences in the data are replaced with U+FFFD.
2222
/// - Parameter data: The data to decode. Must be UTF-8, UTF-16, or UTF-32, and may start with a BOM.
23-
/// - Parameter options: Options that controls JSON parsing. Defaults to no options. See `JSONOptions` for details.
23+
/// - Parameter options: Options that control JSON parsing. Defaults to no options. See `JSONOptions` for details.
2424
/// - Returns: A `JSON` value.
2525
/// - Throws: `JSONParserError` if the data does not contain valid JSON.
2626
public static func decode(_ data: Data, options: JSONOptions = []) throws -> JSON {
@@ -66,6 +66,23 @@
6666
}
6767
}
6868

69+
extension JSON {
70+
/// Returns a `JSONParser` that parses the given `Data` as JSON.
71+
/// - Note: Invalid unicode sequences in the data are replaced with U+FFFD.
72+
/// - Parameter data: The data to parse. Must be UTF-8, UTF-16, or UTF-32, and may start with a BOM.
73+
/// - Parameter options: Options that control JSON parsing. Defaults to no options. See `JSONParserOptions` for details.
74+
/// - Returns: A `JSONParser` value.
75+
public static func parser(for data: Data, options: JSONParserOptions = []) -> JSONParser<AnySequence<UnicodeScalar>> {
76+
if let endian = UTF32Decoder.encodes(data) {
77+
return JSONParser(AnySequence(UTF32Decoder(data: data, endian: endian)), options: options)
78+
} else if let endian = UTF16Decoder.encodes(data) {
79+
return JSONParser(AnySequence(UTF16Decoder(data: data, endian: endian)), options: options)
80+
} else {
81+
return JSONParser(AnySequence(UTF8Decoder(data: data)), options: options)
82+
}
83+
}
84+
}
85+
6986
extension JSON {
7087
/// Converts a JSON-compatible Foundation object into a `JSON` value.
7188
/// - Throws: `JSONFoundationError` if the object is not JSON-compatible.

Tests/JSONParserTests.swift

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,10 +54,52 @@ class JSONParserTests: XCTestCase {
5454
XCTFail()
5555
}
5656
}
57+
58+
func testUTF32Parse() {
59+
let input = "{ \"msg\": \"안녕하세요\" }"
60+
block: do {
61+
guard let data = input.data(using: .utf32) else { XCTFail("Could not get UTF-32 data"); break block }
62+
assertParserEvents(JSON.parser(for: data), [.objectStart, .stringValue("msg"), .stringValue("안녕하세요"), .objectEnd])
63+
}
64+
block: do {
65+
guard let data = input.data(using: .utf32BigEndian) else { XCTFail("Could not get UTF-32BE data"); break block }
66+
assertParserEvents(JSON.parser(for: data), [.objectStart, .stringValue("msg"), .stringValue("안녕하세요"), .objectEnd])
67+
}
68+
block: do {
69+
guard let data = input.data(using: .utf32LittleEndian) else { XCTFail("Could not get UTF-32LE data"); break block }
70+
assertParserEvents(JSON.parser(for: data), [.objectStart, .stringValue("msg"), .stringValue("안녕하세요"), .objectEnd])
71+
}
72+
}
73+
74+
func testUTF16Parse() {
75+
let input = "{ \"msg\": \"안녕하세요\" }"
76+
block: do {
77+
guard let data = input.data(using: .utf16) else { XCTFail("Could not get UTF-16 data"); break block }
78+
assertParserEvents(JSON.parser(for: data), [.objectStart, .stringValue("msg"), .stringValue("안녕하세요"), .objectEnd])
79+
}
80+
block: do {
81+
guard let data = input.data(using: .utf16BigEndian) else { XCTFail("Could not get UTF-16BE data"); break block }
82+
assertParserEvents(JSON.parser(for: data), [.objectStart, .stringValue("msg"), .stringValue("안녕하세요"), .objectEnd])
83+
}
84+
block: do {
85+
guard let data = input.data(using: .utf16LittleEndian) else { XCTFail("Could not get UTF-16LE data"); break block }
86+
assertParserEvents(JSON.parser(for: data), [.objectStart, .stringValue("msg"), .stringValue("안녕하세요"), .objectEnd])
87+
}
88+
}
89+
90+
func testUTF8Parse() {
91+
let input = "{ \"msg\": \"안녕하세요\" }"
92+
guard let data = input.data(using: .utf8) else { return XCTFail("Could not get UTF-8 data") }
93+
assertParserEvents(JSON.parser(for: data), [.objectStart, .stringValue("msg"), .stringValue("안녕하세요"), .objectEnd])
94+
}
5795
}
5896

5997
private func assertParserEvents(_ input: String, streaming: Bool = false, _ events: [JSONEvent], file: StaticString = #file, line: UInt = #line) {
6098
let parser = JSONParser(input.unicodeScalars, options: JSONParserOptions(streaming: streaming))
99+
assertParserEvents(parser, events, file: file, line: line)
100+
}
101+
102+
private func assertParserEvents<Seq: Sequence>(_ parser: JSONParser<Seq>, _ events: [JSONEvent], file: StaticString = #file, line: UInt = #line) where Seq.Iterator.Element == UnicodeScalar {
61103
var iter = parser.makeIterator()
62104
for (i, expected) in events.enumerated() {
63105
guard let event = iter.next() else {

0 commit comments

Comments
 (0)