Skip to content

Commit c3fd6ec

Browse files
committed
JSONSerialization: Add support for .withoutEscapingSlashes, .fragmentsAllowed
1 parent 99f82c6 commit c3fd6ec

File tree

2 files changed

+36
-7
lines changed

2 files changed

+36
-7
lines changed

Sources/Foundation/JSONSerialization.swift

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ extension JSONSerialization {
2525

2626
public static let prettyPrinted = WritingOptions(rawValue: 1 << 0)
2727
public static let sortedKeys = WritingOptions(rawValue: 1 << 1)
28+
public static let fragmentsAllowed = WritingOptions(rawValue: 1 << 2)
29+
public static let withoutEscapingSlashes = WritingOptions(rawValue: 1 << 3)
2830
}
2931
}
3032

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

141143
var writer = JSONWriter(
142-
pretty: opt.contains(.prettyPrinted),
143-
sortedKeys: opt.contains(.sortedKeys),
144+
options: opt,
144145
writer: { (str: String?) in
145146
if let str = str {
146147
jsonStr.append(contentsOf: str.utf8)
@@ -157,7 +158,10 @@ open class JSONSerialization : NSObject {
157158
} else if let container = value as? Dictionary<AnyHashable, Any> {
158159
try writer.serializeJSON(container)
159160
} else {
160-
fatalError("Top-level object was not NSArray or NSDictionary") // This is a fatal error in objective-c too (it is an NSInvalidArgumentException)
161+
guard opt.contains(.fragmentsAllowed) else {
162+
fatalError("Top-level object was not NSArray or NSDictionary") // This is a fatal error in objective-c too (it is an NSInvalidArgumentException)
163+
}
164+
try writer.serializeJSON(value)
161165
}
162166

163167
let count = jsonStr.count
@@ -302,11 +306,13 @@ private struct JSONWriter {
302306
var indent = 0
303307
let pretty: Bool
304308
let sortedKeys: Bool
309+
let withoutEscapingSlashes: Bool
305310
let writer: (String?) -> Void
306311

307-
init(pretty: Bool = false, sortedKeys: Bool = false, writer: @escaping (String?) -> Void) {
308-
self.pretty = pretty
309-
self.sortedKeys = sortedKeys
312+
init(options: JSONSerialization.WritingOptions, writer: @escaping (String?) -> Void) {
313+
pretty = options.contains(.prettyPrinted)
314+
sortedKeys = options.contains(.sortedKeys)
315+
withoutEscapingSlashes = options.contains(.withoutEscapingSlashes)
310316
self.writer = writer
311317
}
312318

@@ -380,7 +386,8 @@ private struct JSONWriter {
380386
case "\\":
381387
writer("\\\\") // U+005C reverse solidus
382388
case "/":
383-
writer("\\/") // U+002F solidus
389+
if !withoutEscapingSlashes { writer("\\") }
390+
writer("/") // U+002F solidus
384391
case "\u{8}":
385392
writer("\\b") // U+0008 backspace
386393
case "\u{c}":

Tests/Foundation/Tests/TestJSONSerialization.swift

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1013,6 +1013,8 @@ extension TestJSONSerialization {
10131013
("test_serialize_Decimal", test_serialize_Decimal),
10141014
("test_serialize_NSDecimalNumber", test_serialize_NSDecimalNumber),
10151015
("test_serialize_stringEscaping", test_serialize_stringEscaping),
1016+
("test_serialize_fragments", test_serialize_fragments),
1017+
("test_serialize_withoutEscapingSlashes", test_serialize_withoutEscapingSlashes),
10161018
("test_jsonReadingOffTheEndOfBuffers", test_jsonReadingOffTheEndOfBuffers),
10171019
("test_jsonObjectToOutputStreamBuffer", test_jsonObjectToOutputStreamBuffer),
10181020
("test_jsonObjectToOutputStreamFile", test_jsonObjectToOutputStreamFile),
@@ -1371,6 +1373,26 @@ extension TestJSONSerialization {
13711373
XCTAssertEqual(try trySerialize(json), "[\"j\\/\"]")
13721374
}
13731375

1376+
func test_serialize_fragments() {
1377+
XCTAssertEqual(try trySerialize(2, options: .fragmentsAllowed), "2")
1378+
XCTAssertEqual(try trySerialize(false, options: .fragmentsAllowed), "false")
1379+
XCTAssertEqual(try trySerialize(true, options: .fragmentsAllowed), "true")
1380+
XCTAssertEqual(try trySerialize(Float(1), options: .fragmentsAllowed), "1")
1381+
XCTAssertEqual(try trySerialize(Double(2), options: .fragmentsAllowed), "2")
1382+
XCTAssertEqual(try trySerialize(Decimal(Double.leastNormalMagnitude), options: .fragmentsAllowed), "0.0000000000000000000000000000000000000000000000000002225073858507201792")
1383+
XCTAssertEqual(try trySerialize("test", options: .fragmentsAllowed), "\"test\"")
1384+
}
1385+
1386+
func test_serialize_withoutEscapingSlashes() {
1387+
// .withoutEscapingSlashes controls whether a "/" is encoded as "\\/" or "/"
1388+
let testString = "This /\\/ is a \\ \\\\ \\\\\\ \"string\"\n\r\t\u{0}\u{1}\u{8}\u{c}\u{f}"
1389+
let escapedString = "\"This \\/\\\\\\/ is a \\\\ \\\\\\\\ \\\\\\\\\\\\ \\\"string\\\"\\n\\r\\t\\u0000\\u0001\\b\\f\\u000f\""
1390+
let unescapedString = "\"This /\\\\/ is a \\\\ \\\\\\\\ \\\\\\\\\\\\ \\\"string\\\"\\n\\r\\t\\u0000\\u0001\\b\\f\\u000f\""
1391+
1392+
XCTAssertEqual(try trySerialize(testString, options: .fragmentsAllowed), escapedString)
1393+
XCTAssertEqual(try trySerialize(testString, options: [.withoutEscapingSlashes, .fragmentsAllowed]), unescapedString)
1394+
}
1395+
13741396
/* These are a programming error and should not be done
13751397
Ideally the interface for JSONSerialization should at compile time prevent this type of thing
13761398
by overloading the interface such that it can only accept dictionaries and arrays.

0 commit comments

Comments
 (0)