Add JSONDecoder decode function with JSONObject

When we decode json with JSONDecoder, we can decode json only use json data with
func decode<T>(T.Type, from data: Data) -> T.
Sometimes, We need interface like func decode<T>(T.Type, from jsonObject: Any) -> T to decode json with json object(ex, Legacy project...).
I know we can use that method with converting json object to json data using JSONSerialization

let jsonData = try! JSONSerialization.data(withJSONObject obj: jsonObject)
let object = try! JSONDecoder().decode(Object.self, from: jsonData)

But, I think that's inconvenient and unneeded step.

As far as I know, func decode<T>(T.Type, from data: Data) -> T method converts json data to json object in swift-corelibs-foundation.

// MARK: - Decoding Values
    /// Decodes a top-level value of the given type from the given JSON representation.
    ///
    /// - parameter type: The type of the value to decode.
    /// - parameter data: The data to decode from.
    /// - returns: A value of the requested type.
    /// - throws: `DecodingError.dataCorrupted` if values requested from the payload are corrupted, or if the given data is not valid JSON.
    /// - throws: An error if any value throws an error during decoding.
    open func decode<T : Decodable>(_ type: T.Type, from data: Data) throws -> T {
        let topLevel: Any
        do {
            topLevel = try JSONSerialization.jsonObject(with: data, options: .allowFragments)
        } catch {
            throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: [], debugDescription: "The given data was not valid JSON.", underlyingError: error))
        }

        let decoder = _JSONDecoder(referencing: topLevel, options: self.options)
        guard let value = try decoder.unbox(topLevel, as: type) else {
            throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: [], debugDescription: "The given data did not contain a top-level value."))
        }

        return value
    }

So, I suggest to JSONDecoder to add an interface to decode with json object.

open func decode<T : Decodable>(_ type: T.Type, from jsonObject: Any) throws -> T {
// Maybe JSONSerialization.isValidJSONObject is unneeded. Because If jsonObject isn't valid JSONObject, decoding will be failed. 
    guard (jsonObject is [Any?] || jsonObject is [String: Any?]) else {
        throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: [], debugDescription: "The given data was not valid JSON.", underlyingError: error))
    }
    
    let decoder = _JSONDecoder(referencing: jsonObject, options: self.options)
    guard let value = try decoder.unbox(jsonObject, as: type) else {
        throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: [], debugDescription: "The given data did not contain a top-level value."))
    }
    
    return value
}

I think that solution is simple and easy to implementation.
And It will be helpfult when we need decode two jsonobject to one model.
We don't have to three models to decode two jsonobject to one model.

/// When we decode two json data with func decode<T>(T.Type, from data: Data) -> T
let object1 = try! JSONDecoder().decode(Object1.self, from: jsonData)
let object2 = try! JSONDecoder().decode(Object2.self, from: jsonData)
let obejct = Object.init(object1, object2)

/// If JSONDecoder privides  decode<T : Decodable>(_ type: T.Type, from jsonObject: Any) throws -> T  We can define only one model.
jsonObject2.forEach {
    jsonObject1[$0.key] = $0.value
}

let object = try! JSONDecoder().decode(Object.self, from: jsonObject1) // Isn't it simple??

Thank you for reading and happy coding:)

Terms of Service

Privacy Policy

Cookie Policy