Is there any way to decode from Any type JSON Object using JSONDecoder?


(Masaki Haga) #1

Hi Swift-Users,

I was wondering if there is any way to decode JSON from Any type JSON
Object using `JSONDecoder`, not from Data type object.

Currently, `JSONDecoder` has only one decode function which decodes Data
type object to `Decodable`. Inside the function, it serializes Data object
to Any Type JSON Object using `JSONSerialization` and pass it into
`_JSONDecoder(referencing:, options:)` (Refer JSONEncoder.swift#874).

As discussed in some of other threads such as "SE-0166: Swift Archival &
Serialization", the default implementation of JSONDecoder or Decodable
protocol doesn’t allow to decode from one format to another format (such as
snake-case to camel-case), we need to implement custom CodingKey enums.
However, in our project, to parse the server API JSON response with
snake-case, declaring custom CodingKey enums for all the pre-existing
models is almost impossible and very inefficient, so I decided to covert
all the JSON keys from snake-case to camel-case, and then pass it into
`JSONDecoder.decode`. To achieve this, we need to convert the Data object
resulted from `URLSession.task` to Any type JSON Object using
`JSONSerialization`, do the conversion from snake-case to camel-case and
then convert back to Data type and then pass to `JSONDecoder.decode` which
looks very redundant because the function uses `JSONSerialization` inside
of it as mentioned above. If there is a function like below, we can get rid
of this redundant call of `JSONSerialization`.

func decode<T : Decodable>(_ type: T.Type, from JSONObject: Any) throws -> T

Sorry if I am misunderstanding the new API but is there any way to decode
`Decodable` directly from Any type JSON Object?

If not, I think adding the function aforementioned and giving an ability to
opt-out this JSON serialization call would give more flexibility to the API
in my humble opinion.

Thank you for reading.

All the best,
- Masaki


(Tony Parker) #2

Hi Masaki,

Do you mean that you are going through the JSON as a string value and changing the keys, then you want to pass this re-written JSON to the decoder?

- Tony

···

On Jun 21, 2017, at 6:58 PM, Masaki Haga via swift-users <swift-users@swift.org> wrote:

Hi Swift-Users,

I was wondering if there is any way to decode JSON from Any type JSON Object using `JSONDecoder`, not from Data type object.

Currently, `JSONDecoder` has only one decode function which decodes Data type object to `Decodable`. Inside the function, it serializes Data object to Any Type JSON Object using `JSONSerialization` and pass it into `_JSONDecoder(referencing:, options:)` (Refer JSONEncoder.swift#874).

As discussed in some of other threads such as "SE-0166: Swift Archival & Serialization", the default implementation of JSONDecoder or Decodable protocol doesn’t allow to decode from one format to another format (such as snake-case to camel-case), we need to implement custom CodingKey enums. However, in our project, to parse the server API JSON response with snake-case, declaring custom CodingKey enums for all the pre-existing models is almost impossible and very inefficient, so I decided to covert all the JSON keys from snake-case to camel-case, and then pass it into `JSONDecoder.decode`. To achieve this, we need to convert the Data object resulted from `URLSession.task` to Any type JSON Object using `JSONSerialization`, do the conversion from snake-case to camel-case and then convert back to Data type and then pass to `JSONDecoder.decode` which looks very redundant because the function uses `JSONSerialization` inside of it as mentioned above. If there is a function like below, we can get rid of this redundant call of `JSONSerialization`.

func decode<T : Decodable>(_ type: T.Type, from JSONObject: Any) throws -> T

Sorry if I am misunderstanding the new API but is there any way to decode `Decodable` directly from Any type JSON Object?

If not, I think adding the function aforementioned and giving an ability to opt-out this JSON serialization call would give more flexibility to the API in my humble opinion.

Thank you for reading.

All the best,
- Masaki
_______________________________________________
swift-users mailing list
swift-users@swift.org
https://lists.swift.org/mailman/listinfo/swift-users


(Masaki Haga) #3

(Added swift users as CC)

···

2017-06-24 3:47 GMT+09:00 Masaki Haga <hgmsk1985@gmail.com>:

Hi Tony,

Got it. Thank you very much for taking time for this.

2017-06-24 1:40 GMT+09:00 Tony Parker <anthony.parker@apple.com>:

Hi Masaki,

Thanks for the additional info. I have a slightly different idea on how
to approach this which I think will be more performant. Let me work on that
and get back to you.

- Tony

On Jun 22, 2017, at 7:51 PM, Masaki Haga <hgmsk1985@gmail.com> wrote:

Hi Tony,

Thank you very much for replying. Let me clarify what I was saying.

Let’s say, we have a JSON like this:

{
"badge": 1,
"content_available": false,
"mutable_content": false
}

and to decode this JSON, we have to have a Codable model like below with
custom CodingKeys enum:

struct APS: Codable {
    enum CodingKeys: String, CodingKey {
        case badge = "badge"
        case contentAvailable = "content_available"
        case mutableContent = "mutable_content"
    }

    var badge: Int
    var contentAvailable: Bool
    var mutableContent: Bool
}

This is a very small JSON so it is not hard to write this custom enum.
However, it is very cumbersome to define this enum in all of the
pre-existing models in our project to be able to decoded by Decodable and
we rather prefer just to have a model something like this:

struct APS: Codable {
    var badge: Int
    var contentAvailable: Bool
    var mutableContent: Bool
}

To be able to decode this model with JSONDecoder, I wrote a JSON
converter like this:

extension String {
    var camelcased: String {
        return self
            .components(separatedBy: "_")
            .enumerated()
            .map { 0 == $0.offset ? $0.element : $0.element.capitalized }
            .joined()
    }
}
// This extension above was referenced from an article written in
Japanese: http://qiita.com/takasek/items/77955948fe283758ee55

struct JSONCaseConverter {
    public static func process(_ JSONObject: Any) -> Any {
        if var dict = JSONObject as? [String: Any] {
            for (key, value) in dict {
                dict[key.camelcased] = process(value)
                dict.removeValue(forKey: key)
            }
            return dict
        } else if let array = JSONObject as? [Any] {
            return array.map(process)
        } else {
            return JSONObject
        }
    }
}

Basically, this JSONCaseConverter go though all the keys in a JSON and
convert the key from snake-case to camel-case so that the JSON can be
decoded directly with the model without custom CodingKeys enum.

And then if we have a Data type JSON object (typically got from
URLSession.dataTask) and want to do some processing like this and decode
with JSONDecoder, we need to do:

1. Serialize Data object with JSONSerialization.jsonObject(with:) and
get Any type JSON Object
2. do some processing to the Any type JSON Object
3. Serialize Any type Object with JSONSerialization.data(withJSONObject:)
and get Data type JSON Object back.
4. and then call JSONDecoder.decode().

However, JSONSerialization.jsonObject(with:) is called again in
JSONDecoder.decode() implementation so there is a computational redundancy.

Because I have already seen several this camel-case vs snake-case
discussion in some places including Swift Evolution, I guess not a small
number of developers will take the similar apploach ( I understand
automatic key renaming could be a unsafe operation and this is just my
personal opinion).

Anyways, I was wondering if there is any way to opt-out the
JSONSerialization.jsonObject(with:) in JSONDecoder.decode(). And if not,
is it a good idea to have one more API such as `decode<T>(_ type: T.Type,
from JSONObject: Any)` which I think gives more flexibility to the API?

Regards,
- Masaki

2017-06-23 8:01 GMT+09:00 Tony Parker <anthony.parker@apple.com>:

Hi Masaki,

Do you mean that you are going through the JSON as a string value and
changing the keys, then you want to pass this re-written JSON to the
decoder?

- Tony

On Jun 21, 2017, at 6:58 PM, Masaki Haga via swift-users < >>> swift-users@swift.org> wrote:

Hi Swift-Users,

I was wondering if there is any way to decode JSON from Any type JSON
Object using `JSONDecoder`, not from Data type object.

Currently, `JSONDecoder` has only one decode function which decodes Data
type object to `Decodable`. Inside the function, it serializes Data object
to Any Type JSON Object using `JSONSerialization` and pass it into
`_JSONDecoder(referencing:, options:)` (Refer JSONEncoder.swift#874).

As discussed in some of other threads such as "SE-0166: Swift Archival &
Serialization", the default implementation of JSONDecoder or Decodable
protocol doesn’t allow to decode from one format to another format (such as
snake-case to camel-case), we need to implement custom CodingKey enums.
However, in our project, to parse the server API JSON response with
snake-case, declaring custom CodingKey enums for all the pre-existing
models is almost impossible and very inefficient, so I decided to covert
all the JSON keys from snake-case to camel-case, and then pass it into
`JSONDecoder.decode`. To achieve this, we need to convert the Data object
resulted from `URLSession.task` to Any type JSON Object using
`JSONSerialization`, do the conversion from snake-case to camel-case and
then convert back to Data type and then pass to `JSONDecoder.decode` which
looks very redundant because the function uses `JSONSerialization` inside
of it as mentioned above. If there is a function like below, we can get rid
of this redundant call of `JSONSerialization`.

func decode<T : Decodable>(_ type: T.Type, from JSONObject: Any) throws
-> T

Sorry if I am misunderstanding the new API but is there any way to
decode `Decodable` directly from Any type JSON Object?

If not, I think adding the function aforementioned and giving an ability
to opt-out this JSON serialization call would give more flexibility to the
API in my humble opinion.

Thank you for reading.

All the best,
- Masaki
_______________________________________________
swift-users mailing list
swift-users@swift.org
https://lists.swift.org/mailman/listinfo/swift-users