Skip to content

Commit 276c55c

Browse files
Fixed AnyObject -> Any and added tests
1 parent e3c27b2 commit 276c55c

File tree

2 files changed

+211
-69
lines changed

2 files changed

+211
-69
lines changed

Foundation/NSJSONSerialization.swift

Lines changed: 103 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -92,77 +92,12 @@ public class NSJSONSerialization : NSObject {
9292

9393
/* Generate JSON data from a Foundation object. If the object will not produce valid JSON then an exception will be thrown. Setting the NSJSONWritingPrettyPrinted option will generate JSON with whitespace designed to make the output more readable. If that option is not set, the most compact possible JSON will be generated. If an error occurs, the error parameter will be set and the return value will be nil. The resulting data is a encoded in UTF-8.
9494
*/
95-
public class func dataWithJSONObject(obj: AnyObject, options opt: NSJSONWritingOptions) throws -> NSData {
96-
let newIndent = opt.contains(NSJSONWritingOptions.PrettyPrinted) ? "\t" : ""
97-
let newLine = opt.contains(NSJSONWritingOptions.PrettyPrinted) ? "\n" : ""
98-
let spacing = opt.contains(NSJSONWritingOptions.PrettyPrinted) ? " " : ""
99-
let str = NSString(try JSONSerialize(obj, newIndent: newIndent, newLine: newLine, space: spacing))
95+
public class func dataWithJSONObject(obj: Any, options opt: NSJSONWritingOptions) throws -> NSData {
96+
let (newIndent, newLine, spacing) = opt.contains(NSJSONWritingOptions.PrettyPrinted) ? ("\t", "\n", " ") : ("", "", "")
97+
let str = NSString(try JSONSerialize(obj, newIndent: newIndent, newLine: newLine, space: spacing, topLevel: true))
10098
return str.dataUsingEncoding(NSUTF8StringEncoding)!
10199
}
102100

103-
private class func JSONSerialize(object: AnyObject, newIndent: String, newLine: String, space: String, indentation: String = "") throws -> String
104-
{
105-
if let array = object as? [AnyObject]
106-
{
107-
guard array.count > 0
108-
else
109-
{
110-
return "\(indentation)[]"
111-
}
112-
return try array.reduce("\(indentation)[\(newLine)", combine: {
113-
let separator = ($1 === array.last) ? "\(newLine)\(indentation)]" : ",\(newLine)"
114-
return $0 + (try JSONSerialize($1, newIndent: newIndent, newLine: newLine, space: space, indentation: indentation + newIndent)) + separator
115-
})
116-
}
117-
else if let dict = object as? [String : AnyObject]
118-
{
119-
guard dict.count > 0
120-
else
121-
{
122-
return "\(indentation){}"
123-
}
124-
var index = 0
125-
return try dict.reduce("\(indentation){\(newLine)", combine: {
126-
let valueString : String
127-
if let str = $1.1 as? String
128-
{
129-
let string = NSString(NSString(str).stringByReplacingOccurrencesOfString("\\", withString: "\\\\")).stringByReplacingOccurrencesOfString("\"", withString: "\\\"")
130-
// TODO: Escape control characters in string as required by RFC
131-
valueString = "\"\(string)\""
132-
}
133-
// TODO: Use NSNumber instead of NSDecimalNumber once implemented properly.
134-
else if let num = $1.1 as? NSDecimalNumber
135-
{
136-
if String(CString: num.objCType, encoding: NSUTF8StringEncoding) == "c"
137-
{
138-
// If objCType == c, its a boolean
139-
valueString = num.boolValue ? "true" : "false"
140-
}
141-
else
142-
{
143-
// TODO: Use stringValue of NSNumber, once it is complete
144-
valueString = num.descriptionWithLocale(nil)
145-
}
146-
}
147-
else if let _ = $1.1 as? NSNull
148-
{
149-
valueString = "null"
150-
}
151-
else
152-
{
153-
valueString = try JSONSerialize($1.1, newIndent: newIndent, newLine: newLine, space: space, indentation: indentation + newIndent)
154-
}
155-
++index
156-
let separator = (index == dict.count) ? "\(newLine)\(indentation)}" : ",\(newLine)"
157-
return $0 + "\(indentation + newIndent)\"" + $1.0 + "\"\(space):\(space)" + valueString + separator
158-
})
159-
}
160-
else
161-
{
162-
throw NSCocoaError.PropertyListWriteInvalidError
163-
}
164-
}
165-
166101
/* Create a Foundation object from JSON data. Set the NSJSONReadingAllowFragments option if the parser should allow top-level objects that are not an NSArray or NSDictionary. Setting the NSJSONReadingMutableContainers option will make the parser generate mutable NSArrays and NSDictionaries. Setting the NSJSONReadingMutableLeaves option will make the parser generate mutable NSString objects. If an error occurs during the parse, then the error parameter will be set and the result will be nil.
167102
The data must be in one of the 5 supported encodings listed in the JSON specification: UTF-8, UTF-16LE, UTF-16BE, UTF-32LE, UTF-32BE. The data may or may not have a BOM. The most efficient encoding to use for parsing is UTF-8, so if you have a choice in encoding the data passed to this method, use UTF-8.
168103
*/
@@ -190,7 +125,106 @@ public class NSJSONSerialization : NSObject {
190125
NSUnimplemented()
191126
}
192127
}
193-
128+
// MARK: - Serialization
129+
private extension NSJSONSerialization
130+
{
131+
private class func escapeStringForJSON(string: String) -> String
132+
{
133+
// TODO: Escape control characters \u{0} to \u{20} in string as required by RFC
134+
return NSString(NSString(string).stringByReplacingOccurrencesOfString("\\", withString: "\\\\")).stringByReplacingOccurrencesOfString("\"", withString: "\\\"")
135+
}
136+
private class func serializeArray<T>(array: [T], newIndent: String, newLine: String, space: String, indentation: String) throws -> String
137+
{
138+
guard array.count > 0
139+
else
140+
{
141+
return "\(indentation)[]"
142+
}
143+
var index = 0
144+
return try array.reduce("\(indentation)[\(newLine)", combine: {
145+
++index
146+
let separator = (index == array.count) ? "\(newLine)\(indentation)]" : ",\(newLine)"
147+
return $0 + (try JSONSerialize($1, newIndent: newIndent, newLine: newLine, space: space, indentation: indentation + newIndent)) + separator
148+
})
149+
}
150+
151+
private class func serializePOD(object: Any) throws -> String
152+
{
153+
if let str = object as? String
154+
{
155+
return "\"\(escapeStringForJSON(str))\""
156+
}
157+
else if let num = object as? Double
158+
{
159+
// FIXME: Use NSNumber once API is fixed
160+
// Detect numeric type and use true, false keywords or integer type if not double
161+
return "\(num)"
162+
}
163+
else if let _ = object as? NSNull
164+
{
165+
return "null"
166+
}
167+
else
168+
{
169+
throw NSCocoaError.PropertyListWriteInvalidError
170+
}
171+
}
172+
173+
private class func serializeObject<T>(object: [String: T], newIndent: String, newLine: String, space: String, indentation: String) throws -> String
174+
{
175+
guard object.count > 0
176+
else
177+
{
178+
return "\(indentation){}"
179+
}
180+
var index = 0
181+
return try object.reduce("\(indentation){\(newLine)", combine: {
182+
let valueString : String
183+
do
184+
{
185+
valueString = try serializePOD($1.1)
186+
}
187+
catch
188+
{
189+
valueString = try JSONSerialize($1.1, newIndent: newIndent, newLine: newLine, space: space, indentation: indentation + newIndent)
190+
}
191+
++index
192+
let separator = (index == object.count) ? "\(newLine)\(indentation)}" : ",\(newLine)"
193+
return $0 + "\(indentation + newIndent)\"" + escapeStringForJSON($1.0) + "\"\(space):\(space)" + valueString + separator
194+
})
195+
}
196+
197+
private class func JSONSerialize(object: Any, newIndent: String, newLine: String, space: String, topLevel: Bool = false, indentation: String = "") throws -> String
198+
{
199+
if let array = object as? [Any]
200+
{
201+
return try serializeArray(array, newIndent: newIndent, newLine: newLine, space: space, indentation: indentation)
202+
}
203+
else if let dict = object as? [String : Any]
204+
{
205+
return try serializeObject(dict, newIndent: newIndent, newLine: newLine, space: space, indentation: indentation)
206+
}
207+
else if let array = object as? NSArray
208+
{
209+
// FIXME: Warns that NSArray can't be casted to [AnyObject]
210+
return try serializeArray(array as! [AnyObject], newIndent: newIndent, newLine: newLine, space: space, indentation: indentation)
211+
}
212+
else if let dict = object as? NSDictionary
213+
{
214+
// FIXME: Warns that NSArray can't be casted to [String: AnyObject]
215+
return try serializeObject(dict as! [String: AnyObject], newIndent: newIndent, newLine: newLine, space: space, indentation: indentation)
216+
}
217+
else
218+
{
219+
guard !topLevel
220+
else
221+
{
222+
throw NSCocoaError.PropertyListWriteInvalidError
223+
}
224+
return try serializePOD(object)
225+
}
226+
}
227+
}
194228
//MARK: - Deserialization
195229
internal extension NSJSONSerialization {
196230

TestFoundation/TestNSJSONSerialization.swift

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ class TestNSJSONSerialization : XCTestCase {
2323
return JSONObjectWithDataTests
2424
+ deserializationTests
2525
+ isValidJSONObjectTests
26+
+ serializationTests
2627
}
2728

2829
}
@@ -397,6 +398,113 @@ extension TestNSJSONSerialization {
397398
}
398399

399400
}
401+
// MARK: - JSONSerialization
402+
extension TestNSJSONSerialization {
403+
404+
var serializationTests: [(String, () -> ())] {
405+
return [
406+
("test_serialize_emptyObject", test_serialize_emptyObject),
407+
("test_serialize_multiStringObject", test_serialize_multiStringObject),
408+
("test_serialize_complicatedObject", test_serialize_complicatedObject),
409+
410+
("test_serialize_emptyArray", test_serialize_emptyArray),
411+
("test_serialize_multiStringArray", test_serialize_multiPODArray),
412+
413+
("test_serialize_values", test_serialize_nested),
414+
]
415+
}
416+
417+
func trySerialize(subject: Any) throws -> String
418+
{
419+
let data = try NSJSONSerialization.dataWithJSONObject(subject, options: [])
420+
guard let string = NSString(data: data, encoding: NSUTF8StringEncoding)
421+
else {
422+
XCTFail("Unable to convert data to string")
423+
return ""
424+
}
425+
return string._swiftObject
426+
}
427+
428+
//MARK: - Object Serialization
429+
func test_serialize_emptyObject() {
430+
431+
do {
432+
XCTAssertEqual(try trySerialize([String: Any]()), "{}")
433+
XCTAssertEqual(try trySerialize([String: NSNumber]()), "{}")
434+
XCTAssertEqual(try trySerialize([String: String]()), "{}")
435+
var json = [String: Float]()
436+
json["s"] = 1.0
437+
json["s"] = nil
438+
XCTAssertEqual(try trySerialize(json), "{}")
439+
}
440+
catch {
441+
XCTFail("Error thrown: \(error)")
442+
}
443+
}
444+
445+
func test_serialize_multiStringObject() {
446+
do {
447+
var json = [String: String]()
448+
json["hello"] = "world"
449+
XCTAssertEqual(try trySerialize(json), "{\"hello\":\"world\"}")
450+
// testing escaped characters like " and \
451+
json["swift"] = "is \\ \"awesome\""
452+
XCTAssertEqual(try trySerialize(json), "{\"hello\":\"world\",\"swift\":\"is \\\\ \\\"awesome\\\"\"}")
453+
}
454+
catch {
455+
XCTFail("Error thrown: \(error)")
456+
}
457+
}
458+
459+
func test_serialize_complicatedObject() {
460+
do {
461+
let json = ["a": 4.0, "b": NSNull(), "c": "string", "d": false]
462+
XCTAssertEqual(try trySerialize(json), "{\"b\":null,\"a\":4.0,\"d\":0.0,\"c\":\"string\"}")
463+
}
464+
catch {
465+
XCTFail("Error thrown: \(error)")
466+
}
467+
}
468+
469+
//MARK: - Array Deserialization
470+
func test_serialize_emptyArray() {
471+
do {
472+
XCTAssertEqual(try trySerialize([String]()), "[]")
473+
XCTAssertEqual(try trySerialize([NSNumber]()), "[]")
474+
XCTAssertEqual(try trySerialize([Any]()), "[]")
475+
XCTAssertEqual(try trySerialize([AnyObject]()), "[]")
476+
}
477+
catch {
478+
XCTFail("Error thrown: \(error)")
479+
}
480+
}
481+
482+
func test_serialize_multiPODArray() {
483+
484+
do {
485+
XCTAssertEqual(try trySerialize(["hello", "swift⚡️"]), "[\"hello\",\"swift⚡️\"]")
486+
XCTAssertEqual(try trySerialize([1.0, 3.3, 2.3]), "[1.0,3.3,2.3]")
487+
XCTAssertEqual(try trySerialize([NSNull(), "hello", 1.0]), "[null,\"hello\",1.0]")
488+
}
489+
catch {
490+
XCTFail("Error thrown: \(error)")
491+
}
492+
}
493+
494+
//MARK: - Nested Serialization
495+
func test_serialize_nested() {
496+
do {
497+
let first = ["a": 4.0, "b": NSNull(), "c": "string", "d": false]
498+
let second = [NSNull(), "hello", 1.0]
499+
let json = [first, second, NSNull(), "hello", 1.0]
500+
// FIXME: Update decimal points and boolean once using NSNumber
501+
XCTAssertEqual(try trySerialize(json), "[{\"b\":null,\"a\":4.0,\"d\":0.0,\"c\":\"string\"},[null,\"hello\",1.0],null,\"hello\",1.0]")
502+
}
503+
catch {
504+
XCTFail("Error thrown: \(error)")
505+
}
506+
}
507+
}
400508

401509
// MARK: - isValidJSONObjectTests
402510
extension TestNSJSONSerialization {

0 commit comments

Comments
 (0)