Search code examples
swiftnsjsonserialization

Serializing JSON ReadingOptions with inconsistent output in Swift 4.2


I'm working on a pet project where I am serializing JSON using the JSONSerialization class and jsonObject(with:options:). The object is unusable until cast into a Dictionary [String: Any] or an Array [Any]. This is where the inconsistency occurs. The following is a method from one of my classes. The input is tested and valid.

private static func parse(data: Data) -> [JSONDictionary]? {
    do {
        let options = JSONSerialization.ReadingOptions() // rawValue = UInt 0
        let otherOptions: JSONSerialization.ReadingOptions = [] // rawValue = UInt 0
        let jsonAny = try JSONSerialization.jsonObject(with: data, options: otherOptions)
        if let array = jsonAny as? [String: Any] {
            print(array)
        }
    } catch {
        return nil
    }

    return nil
}

Both of the ReadingOption objects are valid and produce valid output that can be properly cast, and print(array) is called.

However, when I use the following, invalid output is returned and can not be cast properly. Note options in the jsonObject call has an equivalent value to otherOptions in the above example.

private static func parse(data: Data) -> [JSONDictionary]? {
    do {
        let jsonAny = try JSONSerialization.jsonObject(with: data, options: [])
        if let array = jsonAny as? [String: Any] {
            print(array) // never called
        }
    } catch {
        return nil
    }

    return nil
}

I thought because they have equivalent values that I could use them in place of each other. But that is not the case. Is this a bug, or am I using this incorrectly?

Edit: here is the dataset being used https://www.govtrack.us/api/v2/role?current=true&role_type=senator


Solution

  • The reading options are irrelevant. In Swift ReadingOptions are only useful if the expected result is not array or dictionary.

    If the expected type is array or dictionary omit the options parameter.

    The inconsistency is that your return type is an array ([JSONDictionary]) but the actual type is a dictionary.

    private static func parse(data: Data) -> JSONDictionary? {
        do {
            let jsonAny = try JSONSerialization.jsonObject(with: data)
            if let jsonDictionary = jsonAny as? JSONDictionary {
                return jsonDictionary 
            }
        } catch {
            print(error)
            return nil
        }
        return nil
    }
    

    It's recommended to hand over an error of a throwing method

    enum SerializationError : Error {
       case typeMismatch
    }
    
    private static func parse(data: Data) throws -> JSONDictionary {
        let jsonAny = try JSONSerialization.jsonObject(with: data)
        guard let jsonDictionary = jsonAny as? JSONDictionary else { throw SerializationError.typeMismatch }
        return jsonDictionary 
    }