Skip to content

Commit

Permalink
JSONSerialization: Add support for .withoutEscapingSlashes, .fragment…
Browse files Browse the repository at this point in the history
…sAllowed
  • Loading branch information
spevans committed Apr 6, 2020
1 parent 99f82c6 commit c3fd6ec
Show file tree
Hide file tree
Showing 2 changed files with 36 additions and 7 deletions.
21 changes: 14 additions & 7 deletions Sources/Foundation/JSONSerialization.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ extension JSONSerialization {

public static let prettyPrinted = WritingOptions(rawValue: 1 << 0)
public static let sortedKeys = WritingOptions(rawValue: 1 << 1)
public static let fragmentsAllowed = WritingOptions(rawValue: 1 << 2)
public static let withoutEscapingSlashes = WritingOptions(rawValue: 1 << 3)
}
}

Expand Down Expand Up @@ -139,8 +141,7 @@ open class JSONSerialization : NSObject {
var jsonStr = [UInt8]()

var writer = JSONWriter(
pretty: opt.contains(.prettyPrinted),
sortedKeys: opt.contains(.sortedKeys),
options: opt,
writer: { (str: String?) in
if let str = str {
jsonStr.append(contentsOf: str.utf8)
Expand All @@ -157,7 +158,10 @@ open class JSONSerialization : NSObject {
} else if let container = value as? Dictionary<AnyHashable, Any> {
try writer.serializeJSON(container)
} else {
fatalError("Top-level object was not NSArray or NSDictionary") // This is a fatal error in objective-c too (it is an NSInvalidArgumentException)
guard opt.contains(.fragmentsAllowed) else {
fatalError("Top-level object was not NSArray or NSDictionary") // This is a fatal error in objective-c too (it is an NSInvalidArgumentException)
}
try writer.serializeJSON(value)
}

let count = jsonStr.count
Expand Down Expand Up @@ -302,11 +306,13 @@ private struct JSONWriter {
var indent = 0
let pretty: Bool
let sortedKeys: Bool
let withoutEscapingSlashes: Bool
let writer: (String?) -> Void

init(pretty: Bool = false, sortedKeys: Bool = false, writer: @escaping (String?) -> Void) {
self.pretty = pretty
self.sortedKeys = sortedKeys
init(options: JSONSerialization.WritingOptions, writer: @escaping (String?) -> Void) {
pretty = options.contains(.prettyPrinted)
sortedKeys = options.contains(.sortedKeys)
withoutEscapingSlashes = options.contains(.withoutEscapingSlashes)
self.writer = writer
}

Expand Down Expand Up @@ -380,7 +386,8 @@ private struct JSONWriter {
case "\\":
writer("\\\\") // U+005C reverse solidus
case "/":
writer("\\/") // U+002F solidus
if !withoutEscapingSlashes { writer("\\") }
writer("/") // U+002F solidus
case "\u{8}":
writer("\\b") // U+0008 backspace
case "\u{c}":
Expand Down
22 changes: 22 additions & 0 deletions Tests/Foundation/Tests/TestJSONSerialization.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1013,6 +1013,8 @@ extension TestJSONSerialization {
("test_serialize_Decimal", test_serialize_Decimal),
("test_serialize_NSDecimalNumber", test_serialize_NSDecimalNumber),
("test_serialize_stringEscaping", test_serialize_stringEscaping),
("test_serialize_fragments", test_serialize_fragments),
("test_serialize_withoutEscapingSlashes", test_serialize_withoutEscapingSlashes),
("test_jsonReadingOffTheEndOfBuffers", test_jsonReadingOffTheEndOfBuffers),
("test_jsonObjectToOutputStreamBuffer", test_jsonObjectToOutputStreamBuffer),
("test_jsonObjectToOutputStreamFile", test_jsonObjectToOutputStreamFile),
Expand Down Expand Up @@ -1371,6 +1373,26 @@ extension TestJSONSerialization {
XCTAssertEqual(try trySerialize(json), "[\"j\\/\"]")
}

func test_serialize_fragments() {
XCTAssertEqual(try trySerialize(2, options: .fragmentsAllowed), "2")
XCTAssertEqual(try trySerialize(false, options: .fragmentsAllowed), "false")
XCTAssertEqual(try trySerialize(true, options: .fragmentsAllowed), "true")
XCTAssertEqual(try trySerialize(Float(1), options: .fragmentsAllowed), "1")
XCTAssertEqual(try trySerialize(Double(2), options: .fragmentsAllowed), "2")
XCTAssertEqual(try trySerialize(Decimal(Double.leastNormalMagnitude), options: .fragmentsAllowed), "0.0000000000000000000000000000000000000000000000000002225073858507201792")
XCTAssertEqual(try trySerialize("test", options: .fragmentsAllowed), "\"test\"")
}

func test_serialize_withoutEscapingSlashes() {
// .withoutEscapingSlashes controls whether a "/" is encoded as "\\/" or "/"
let testString = "This /\\/ is a \\ \\\\ \\\\\\ \"string\"\n\r\t\u{0}\u{1}\u{8}\u{c}\u{f}"
let escapedString = "\"This \\/\\\\\\/ is a \\\\ \\\\\\\\ \\\\\\\\\\\\ \\\"string\\\"\\n\\r\\t\\u0000\\u0001\\b\\f\\u000f\""
let unescapedString = "\"This /\\\\/ is a \\\\ \\\\\\\\ \\\\\\\\\\\\ \\\"string\\\"\\n\\r\\t\\u0000\\u0001\\b\\f\\u000f\""

XCTAssertEqual(try trySerialize(testString, options: .fragmentsAllowed), escapedString)
XCTAssertEqual(try trySerialize(testString, options: [.withoutEscapingSlashes, .fragmentsAllowed]), unescapedString)
}

/* These are a programming error and should not be done
Ideally the interface for JSONSerialization should at compile time prevent this type of thing
by overloading the interface such that it can only accept dictionaries and arrays.
Expand Down

0 comments on commit c3fd6ec

Please sign in to comment.