Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initialisation using nil or NSNull #22

Closed
cjmconie opened this issue Feb 11, 2019 · 5 comments
Closed

Initialisation using nil or NSNull #22

cjmconie opened this issue Feb 11, 2019 · 5 comments

Comments

@cjmconie
Copy link
Collaborator

cjmconie commented Feb 11, 2019

Currently, initialisation from an Any type does not allow nil or NSNull values (as far as I can tell). Would it make sense to support this?

There are two use cases I have run across:

  1. Initialising using a Swift dictionary containing nil:
let modelUsingNil: Any = [
            "foo": "hello",
            "bar": nil
        ]
        
let json1 = try! JSON(modelUsingNil) // Thread 1: Fatal error: 'try!' expression unexpectedly raised an error
print(json1)
  1. Initialising using an object containing NSNull. For example deserialising a json object JSONSerialization.jsonObject().
let jsonData = try! JSONSerialization.data(withJSONObject: modelUsingNil)
let modelWithNSNull = try! JSONSerialization.jsonObject(with: jsonData)
print(modelWithNSNull)

// see: https://stackoverflow.com/a/28024012/3589408
let json2 = try! JSON(modelWithNSNull) // Thread 1: Fatal error: 'try!' expression unexpectedly raised an error
print(json2)

Potential solution (wip)

For NSNull initialisation, the following could work:

extension JSON {
    public init(_ value: Any) throws {
        switch value {
        case _ as NSNull: 
            self = .null
        case let num as Float:
            self = .number(num)
        case let num as Int:
            self = .number(Float(num))
        case let str as String:
            self = .string(str)
        case let bool as Bool:
            self = .bool(bool)
        case let array as [Any]:
            self = .array(try array.map(JSON.init))
        case let dict as [String: Any]:
            self = .object(try dict.mapValues(JSON.init))
        default:
            throw BVJSONError.decodingError
        }
    }
}
@zoul
Copy link
Collaborator

zoul commented Feb 11, 2019

Makes sense! Short test case:

XCTAssertEqual(try JSON(Optional<Int>.none as Any), .null)
XCTAssertEqual(try JSON(NSNull()), .null)

The proposed NSNull initialization works well, I’ll think about the nil case in a moment.

@zoul
Copy link
Collaborator

zoul commented Feb 11, 2019

This works for me:

--- a/GenericJSON/Initialization.swift
+++ b/GenericJSON/Initialization.swift
@@ -9,6 +9,10 @@ extension JSON {
     /// of those types.
     public init(_ value: Any) throws {
         switch value {
+        case _ as NSNull:
+            self = .null
+        case let opt as Optional<Any> where opt == nil:
+            self = .null
         case let num as Float:
             self = .number(num)
         case let num as Int:

I have to run now, will write some tests to check for corner cases later, the relationship between Any and Optional is a bit iffy.

@zoul
Copy link
Collaborator

zoul commented Feb 11, 2019

I’ve pushed my working version, can you please try that?

@cjmconie
Copy link
Collaborator Author

I have added a further comment to the commit regarding the unit tests, but it is more a question of interest. I would consider this issue resolved. 😄

zoul added a commit that referenced this issue Feb 13, 2019
@zoul
Copy link
Collaborator

zoul commented Feb 13, 2019

🎉

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants