Skip to content

Commit 541e856

Browse files
committed
JSONSerialization: Add support for .withoutEscapingSlashes, .fragmentsAllowed
1 parent 509891d commit 541e856

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
@@ -301,11 +305,13 @@ private struct JSONWriter {
301305
var indent = 0
302306
let pretty: Bool
303307
let sortedKeys: Bool
308+
let withoutEscapingSlashes: Bool
304309
let writer: (String?) -> Void
305310

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

@@ -379,7 +385,8 @@ private struct JSONWriter {
379385
case "\\":
380386
writer("\\\\") // U+005C reverse solidus
381387
case "/":
382-
writer("\\/") // U+002F solidus
388+
if !withoutEscapingSlashes { writer("\\") }
389+
writer("/") // U+002F solidus
383390
case "\u{8}":
384391
writer("\\b") // U+0008 backspace
385392
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)