Got it. Thank you very much for taking time for this.
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: CodingKeyで、case名のcamelCase ⇄ stringValueのsnake_case を自動で変換する #Swift - Qiita
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