[swift-users] Is there any way to decode from Any type JSON Object using JSONDecoder?

Masaki Haga hgmsk1985 at gmail.com
Wed Jun 28 07:09:26 CDT 2017


(Added swift users as CC)

2017-06-24 3:47 GMT+09:00 Masaki Haga <hgmsk1985 at 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 at 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 at 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 at 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 at 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 at swift.org
>>> https://lists.swift.org/mailman/listinfo/swift-users
>>>
>>>
>>>
>>
>>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-users/attachments/20170628/df7a94bd/attachment.html>


More information about the swift-users mailing list