Swift: Constant in Template Definition - ios

I'm working with a backend developer that likes to encapsulate json bodies in another object such as data:
Example:
GET: /user/current:
{
data: {
firstName: "Evan",
lastName: "Stoddard"
}
}
I would simply like to just call json decode on the response to get a User struct that I've created but the added data object requires another struct. To get around this I created a generic template class:
struct DecodableData<DecodableType:Decodable>:Decodable {
var data:DecodableType
}
Now I can get my json payload and if I want to get a User struct just get the data property of my template:
let user = JSONDecoder().decode(DecodableData<User>.self, from: jsonData).data
This is all fine and dandy until sometimes, the key, data, isn't always data.
I feel like this is most likely fairly trivial stuff, but is there a way I can add a parameter in my template definition so I can change the enum coding keys as that data key might change?
Something like the following?
struct DecodableData<DecodableType:Decodable, Key:String>:Decodable {
enum CodingKeys: String, CodingKey {
case data = Key
}
var data:DecodableType
}
This way I can pass in the target decodable class along with the key that encapsulates that object.

No need for coding keys. Instead, you need a simple container that parses the JSON as a dictionary that has exactly one key-value pair, discarding the key.
struct Container<T>: Decodable where T: Decodable {
let value: T
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
let dict = try container.decode([String: T].self)
guard dict.count == 1 else {
throw DecodingError.dataCorruptedError(in: container, debugDescription: "expected exactly 1 key value pair, got \(dict.count)")
}
value = dict.first!.value
}
}
If the JSON is empty or has more than one key-value pair, an exception is raised.
Assuming a simple struct such as
struct Foo: Decodable, Equatable {
let a: Int
}
you can parse it regardless of the key:
let foo1 = try! JSONDecoder().decode(
Container<Foo>.self,
from: #"{ "data": { "a": 1 } }"#.data(using: .utf8)!
).value
let foo2 = try! JSONDecoder().decode(
Container<Foo>.self,
from: #"{ "doesn't matter at all": { "a": 1 } }"#.data(using: .utf8)!
).value
foo1 == foo2 // true
This also works for JSON responses that have null as the value, in which case you need to parse it as an optional of your type:
let foo = try! JSONDecoder().decode(
Container<Foo?>.self,
from: #"{ "data": null }"#.data(using: .utf8)!
).value // nil

Try with something like this:
struct GenericCodingKey: CodingKey {
var stringValue: String
init(value: String) {
self.stringValue = value
}
init?(stringValue: String) {
self.stringValue = stringValue
}
var intValue: Int?
init?(intValue: Int) {
return nil
}
}
struct DecodableData<DecodableType: CustomDecodable>: Decodable {
var data: DecodableType
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: GenericCodingKey.self)
data = try container.decode(DecodableType.self, forKey: GenericCodingKey(value: DecodableType.dataKey))
}
}
protocol CustomDecodable: Decodable {
static var dataKey: String { get }
}
extension CustomDecodable {
static var dataKey: String {
return "data" // This is your default
}
}
struct CustomDataKeyStruct: CustomDecodable {
static var dataKey: String = "different"
}
struct NormalDataKeyStruct: CustomDecodable {
//Change Nothing
}

Related

Swift Codable struct with a generic property

Say we've got a cursor based paginated API where multiple endpoints can be paginated. The response of such an endpoint is always as follows:
{
"nextCursor": "someString",
"PAYLOAD_KEY": <generic response>
}
So the payload always returns a cursor and the payload key depends on the actual endpoint we use. For example if we have GET /users it might be users and the value of the key be an array of objects or we could cal a GET /some-large-object and the key being item and the payload be an object.
Bottom line the response is always an object with a cursor and one other key and it's associated value.
Trying to make this generic in Swift I was thinking of this:
public struct Paginable<Body>: Codable where Body: Codable {
public let body: Body
public let cursor: String?
private enum CodingKeys: String, CodingKey {
case body, cursor
}
}
Now the only issue with this code is that it expects the Body to be accessible under the "body" key which isn't the case.
We could have a struct User: Codable and the paginable specialized as Paginable<[Users]> where the API response object would have the key users for the array.
My question is how can I make this generic Paginable struct work so that I can specify the JSON payload key from the Body type?
The simplest solution I can think of is to let the decoded Body to give you the decoding key:
protocol PaginableBody: Codable {
static var decodingKey: String { get }
}
struct RawCodingKey: CodingKey, Equatable {
let stringValue: String
let intValue: Int?
init(stringValue: String) {
self.stringValue = stringValue
intValue = nil
}
init(intValue: Int) {
stringValue = "\(intValue)"
self.intValue = intValue
}
}
struct Paginable<Body: PaginableBody>: Codable {
public let body: Body
public let cursor: String?
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: RawCodingKey.self)
body = try container.decode(Body.self, forKey: RawCodingKey(stringValue: Body.decodingKey))
cursor = try container.decodeIfPresent(String.self, forKey: RawCodingKey(stringValue: "nextCursor"))
}
}
For example:
let jsonString = """
{
"nextCursor": "someString",
"PAYLOAD_KEY": {}
}
"""
let jsonData = Data(jsonString.utf8)
struct SomeBody: PaginableBody {
static let decodingKey = "PAYLOAD_KEY"
}
let decoder = JSONDecoder()
let decoded = try? decoder.decode(Paginable<SomeBody>.self, from: jsonData)
print(decoded)
Another option is to always take the "other" non-cursor key as the body:
struct Paginable<Body: Codable>: Codable {
public let body: Body
public let cursor: String?
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: RawCodingKey.self)
let cursorKey = RawCodingKey(stringValue: "nextCursor")
cursor = try container.decodeIfPresent(String.self, forKey: cursorKey)
// ! should be replaced with proper decoding error thrown
let bodyKey = container.allKeys.first { $0 != cursorKey }!
body = try container.decode(Body.self, forKey: bodyKey)
}
}
Another possible option is to pass the decoding key directly to JSONDecoder inside userInfo and then access it inside init(from:). That would give you the biggest flexibility but you would have to specify it always during decoding.
You can use generic model with type erasing, for example
struct GenericInfo: Encodable {
init<T: Encodable>(name: String, params: T) {
valueEncoder = {
var container = $0.container(keyedBy: CodingKeys.self)
try container.encode(name, forKey: . name)
try container.encode(params, forKey: .params)
}
}
// MARK: Public
func encode(to encoder: Encoder) throws {
try valueEncoder(encoder)
}
// MARK: Internal
enum CodingKeys: String, CodingKey {
case name
case params
}
let valueEncoder: (Encoder) throws -> Void
}

Is it possible to have JSON string with different field name decoded into same struct Object?

Currently, I have the following 2 JSON string - unoptimized_json and optimized_json.
let unoptimized_json = "[{\"id\":1,\"text\":\"hello\",\"checked\":true}]"
let optimized_json = "[{\"i\":1,\"t\":\"hello\",\"c\":true}]"
I would like to decode them, into same struct Object.
struct Checklist: Hashable, Codable {
let id: Int64
var text: String?
var checked: Bool
enum CodingKeys: String, CodingKey {
case id = "i"
case text = "t"
case checked = "c"
}
func hash(into hasher: inout Hasher) {
hasher.combine(id)
}
static func == (lhs: Checklist, rhs: Checklist) -> Bool {
return lhs.id == rhs.id
}
}
However, current design can only accept format optimized_json and not unoptimized_json.
In Java Android, I can achieve this by using alternate.
import com.google.gson.annotations.SerializedName;
public class Checklist {
#SerializedName(value="i", alternate="id")
private final long id;
#SerializedName(value="t", alternate="text")
private String text;
#SerializedName(value="c", alternate="checked")
private boolean checked;
}
I was wondering, in Swift, do we have equivalent feature to achieve so?
Is it possible to have JSON string with different field name decoded into same struct Object?
There's no way to do that automatically, but you can implement your own decoding logic and attempt the different values there.
struct User: Decodable {
enum CodingKeys: String, CodingKey {
case i, id
}
let id: Int
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let i = try container.decodeIfPresent(Int.self, forKey: .i)
let id = try container.decodeIfPresent(Int.self, forKey: .id)
guard let identifier = i ?? id else { throw NoIDFoundError }
self.id = identifier
}
}
I would suggest using a custom keyDecodingStrategy when decoding and let that strategy return the correct key. This solution assumes that the names of the optimized keys are always the first letter(s) of the normal key. It can be used otherwise as well but then a more hard coded solution is needed.
First we use our own type for the keys, the interesting part is in init(stringValue: String) where we use the CodingKeys enum (see below) of CheckList to get the right key
struct VaryingKey: CodingKey {
var intValue: Int?
init?(intValue: Int) {
self.intValue = intValue
stringValue = "\(intValue)"
}
var stringValue: String
init(stringValue: String) {
self.stringValue = Checklist.CodingKeys.allCases
.first(where: { stringValue == $0.stringValue.prefix(stringValue.count) })?
.stringValue ?? stringValue
}
}
We need to define a CodingKeys enum for checklist and make it conform to CaseIterable for the above code to work
enum CodingKeys: String, CodingKey, CaseIterable {
case id
case text
case type
case checked
}
and then we use this when decoding
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .custom({ codingPath in
let key = codingPath.last!.stringValue
return VaryingKey(stringValue: key)
})
do {
let unoptimized = try decoder.decode([Checklist].self, from: unoptimized_json.data(using: .utf8)!)
let optimized = try decoder.decode([Checklist].self, from: optimized_json.data(using: .utf8)!)
print(unoptimized)
print(optimized)
} catch {
print(error)
}
[__lldb_expr_377.Checklist(id: 1, text: Optional("hello"), type: "world", checked: true)]
[__lldb_expr_377.Checklist(id: 1, text: Optional("hello"), type: "world", checked: true)]
(The extra "type" attribute was only used to test the solution)
Update
I mentioned above that if the short version of the json key couldn't be dynamically deducted from the normal key then a more hard coded solution was needed so for completeness here is an example of such a solution with a new init(stringValue:) in VaryingKey
init(stringValue: String) {
if let codingKey = Checklist.CodingKeys(stringValue: stringValue) {
print(codingKey)
self.stringValue = stringValue
} else {
switch stringValue {
case "i":
self.stringValue = Checklist.CodingKeys.id.stringValue
case "t":
self.stringValue = Checklist.CodingKeys.text.stringValue
case "ty":
self.stringValue = Checklist.CodingKeys.type.stringValue
case "c":
self.stringValue = Checklist.CodingKeys.checked.stringValue
default:
fatalError()
}
}
}
At this time, Swift doesn't have native support for specifying alternate coding keys, however as others have said, alternatives exist, and are not complicate.
What I would do in your scenario would be to push as much as possible to the compiler side, and rely as little as possible on runtime failures, as those can go unnoticed.
These being said, I would use composition here, by declaring two new structs, one optimized and one non-optimized, to decode from:
struct Checklist: Hashable, Codable {
let id: Int64
var text: String?
var checked: Bool
}
extension Checklist {
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
do {
try self.init(container.decode(Optimized.self))
} catch {
try self.init(container.decode(NonOptimized.self))
}
}
private init(_ optimized: Optimized) {
self.init(id: optimized.i,
text: optimized.t,
checked: optimized.c)
}
private init(_ nonOptimized: NonOptimized) {
self.init(id: nonOptimized.id,
text: nonOptimized.text,
checked: nonOptimized.checked)
}
private struct Optimized: Decodable {
let i: Int64
var t: String?
var c: Bool
}
private struct NonOptimized: Decodable {
let id: Int64
var text: String?
var checked: Bool
}
}
This way, when you add a new property, the compiler will help you not forget to add it to the corresponding structs.
The minor disadvantage is that you'll have to maintain two more structs, however those two structs are dummy ones, which don't pose that much maintenance costs. The upside is that you'll benefit the most of the compiler support.

JSON Codable - What to do with [Any]?

I have a JSON with the following structure :
         "properties":[
            {
               "name":"Quality",
               "values":[],
               "displayMode":0,
               "type":6
            },
            {
               "name":"Armour",
               "values":[],
               "displayMode":0,
               "type":16
            },
            {
               "name":"Evasion Rating",
               "values":[],
               "displayMode":0,
               "type":17
            }
         ]
The API is always returning an array for "value" with first element being a String, and second element being an Int.
"values":[
                  [
                     "+26%",
                     1
                  ]
               ],
Here’s how I mapped the JSON so far :
struct Properties: Codable {
var name: String
var values: [Any]
var displayMode: Int
var type: Int
}
At which point Xcode complains because Type 'Properties' does not conform to protocol 'Decodable'
So, I know that Any doesn't conform to codable but the thing is I don't how to convert this [Any] into something Swift can work with...
Can someone share a hint to the solution ?
Thank you very much :)
You need to use an unkeyed container for this when decoding in your custom init(from:).
If you know there is always one string followed by one integer you can define the struct for the values like this
struct Value: Decodable {
let string: String?
let integer: Int?
init(from decoder: Decoder) throws {
var container = try decoder.unkeyedContainer()
string = try container.decodeIfPresent(String.self)
integer = try container.decodeIfPresent(Int.self)
}
}
If there could be more elements you can use a loop instead
struct Value: Decodable {
let strings: [String]
let integers: [Int]
init(from decoder: Decoder) throws {
var container = try decoder.unkeyedContainer()
var strings = [String]()
var integers = [Int]()
while !container.isAtEnd {
do {
let string = try container.decode(String.self)
strings.append(string)
} catch {
let integer = try container.decode(Int.self)
integers.append(integer)
}
}
self.integers = integers
self.strings = strings
}
}

Decode all properties in custom init (enumerate over all properties of a class and assign them a value)

I'm currently working on a project that it's API isn't ready yet. So sometimes type of some properties changes. For example I have this struct:
struct Animal: Codable {
var tag: Int?
var name: String?
var type: String?
var birthday: Date?
}
for this json:
{
"tag": 12,
"name": "Dog",
"type": "TYPE1"
}
But in development, the json changes to something like this:
{
"tag": "ANIMAL",
"name": "Dog",
"type": 1
}
So I get some type mismatch error in decoder and nil object. To prevent decoder from failing entire object, I implement a custom init and set nil for any unknown property and it works like charm (NOTE: I'll handle these changes later and this is only for unplanned and temporary changes):
#if DEBUG
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
tag = (try? container.decodeIfPresent(Int.self, forKey: .tag)) ?? nil
name = (try? container.decodeIfPresent(String.self, forKey: .name)) ?? nil
type = (try? container.decodeIfPresent(String.self, forKey: .type)) ?? nil
birthday = (try? container.decodeIfPresent(Date.self, forKey: .birthday)) ?? nil
}
#endif
But for larger classes and struct, I have to write any property manually and it takes time and more importantly, sometimes I missed a property to set!
So is there any way to enumerate over all properties and set them?
I know I can get all keys from container but don't know how to set it's corresponding property:
for key in container.allKeys {
self.<#corresponding_property#> = (try? container.decodeIfPresent(<#corresponding_type#>.self, forKey: key)) ?? nil
}
Thanks
The problem in your particular example is that the type of your values keeps changing: sometimes tag is a string, sometimes it's an integer. You're going to need a lot more than your Optional approach; that deals with whether something is present, not whether it has the right type. You'll need a union type that can decode and represent a string or an integer, like this:
enum Sint : Decodable {
case string(String)
case int(Int)
enum Err : Error { case oops }
init(from decoder: Decoder) throws {
let con = try decoder.singleValueContainer()
if let s = try? con.decode(String.self) {
self = .string(s)
return
}
if let i = try? con.decode(Int.self) {
self = .int(i)
return
}
throw Err.oops
}
}
Using that, I was able to decode both your examples using a single Animal struct type:
struct Animal: Decodable {
var tag: Sint
var name: String
var type: Sint
}
let j1 = """
{
"tag": 12,
"name": "Dog",
"type": "TYPE1"
}
"""
let j2 = """
{
"tag": "ANIMAL",
"name": "Dog",
"type": 1
}
"""
let d1 = j1.data(using: .utf8)!
let a1 = try! JSONDecoder().decode(Animal.self, from: d1)
let d2 = j2.data(using: .utf8)!
let a2 = try! JSONDecoder().decode(Animal.self, from: d2)
Okay, but now let's say you don't even know what the keys are going to be. Then you need an AnyCodingKey type that can mop up the keys no matter what they are, and instead of multiple properties, your Animal will have a single property that's a Dictionary, like this:
struct Animal: Decodable {
var d = [String : Sint]()
struct AnyCodingKey : CodingKey {
var stringValue: String
var intValue: Int?
init(_ codingKey: CodingKey) {
self.stringValue = codingKey.stringValue
self.intValue = codingKey.intValue
}
init(stringValue: String) {
self.stringValue = stringValue
self.intValue = nil
}
init(intValue: Int) {
self.stringValue = String(intValue)
self.intValue = intValue
}
}
init(from decoder: Decoder) throws {
let con = try decoder.container(keyedBy: AnyCodingKey.self)
for key in con.allKeys {
let result = try con.decode(Sint.self, forKey: key)
self.d[key.stringValue] = result
}
}
}
So now you can decode something with complete unknown keys whose value can be string or integer. Again, this works fine against the JSON examples you gave.
Note that this is the inverse of what you originally asked to do. Instead of using the struct property names to generate the keys, I've simply accepted any key of either type and stored it flexibly in the struct through the use of the dictionary. You could also put a property façade in front of that dictionary using the new Swift 4.2 dynamicMemberLookup feature. But that is left as an exercise for the reader!
The tool you want is Sourcery. It's a meta-programming wrapper for Swift that will write the boilerplate for you, since you know what you want to write, it's just tedious (that's what Sourcery is ideal for). The important thing about Sourcery is (despite the name), there's no magic. It just writes Swift code for you based on other Swift code. That makes it easy to pull out later when you don't need it anymore.
Here is my extension.
In my case, If the type does not match, the default will be nil.
extension KeyedDecodingContainer {
public func decodeIfPresent<T>(_ type: T.Type, forKey key: KeyedDecodingContainer<K>.Key) throws -> T? where T: Decodable {
do {
let result = try decode(type, forKey: key)
return result
} catch {
return nil
}
}
public func decodeIfPresent(_ type: Int.Type, forKey key: KeyedDecodingContainer<K>.Key) throws -> Int? {
do {
let result = try decode(type, forKey: key)
return result
} catch {
return nil
}
}
public func decodeIfPresent(_ type: Bool.Type, forKey key: KeyedDecodingContainer<K>.Key) throws -> Bool? {
do {
let result = try decode(type, forKey: key)
return result
} catch {
return nil
}
}
public func decodeIfPresent(_ type: String.Type, forKey key: KeyedDecodingContainer<K>.Key) throws -> String? {
do {
let result = try decode(type, forKey: key)
return result
} catch {
return nil
}
}
public func decodeIfPresent(_ type: Double.Type, forKey key: KeyedDecodingContainer<K>.Key) throws -> Double? {
do {
let result = try decode(type, forKey: key)
return result
} catch {
return nil
}
}
public func decodeIfPresent(_ type: Float.Type, forKey key: KeyedDecodingContainer<K>.Key) throws -> Float? {
do {
let result = try decode(type, forKey: key)
return result
} catch {
return nil
}
}

Codable object mapping array element to string

I have a (annoying) situation where my back-end returns an object like this:
{
"user": {
"name": [
"John"
],
"familyName": [
"Johnson"
]
}
}
where each property is an array that holds a string as its first element. In my data model struct I could declare each property as an array but that really would be ugly. I would like to have my model as such:
struct User: Codable {
var user: String
var familyName: String
}
But this of course would fail the encoding/decoding as the types don't match. Until now I've used ObjectMapper library which provided a Map object and currentValue property, with that I could declare my properties as String type and in my model init method assig each value through this function:
extension Map {
public func firstFromArray<T>(key: String) -> T? {
if let array = self[key].currentValue as? [T] {
return array.first
}
return self[key].currentValue as? T
}
}
But now that I am converting to Codable approach, I don't know how to do such mapping. Any ideas?
You can override init(from decoder: Decoder):
let json = """
{
"user": {
"name": [
"John"
],
"familyName": [
"Johnson"
]
}
}
"""
struct User: Codable {
var name: String
var familyName: String
init(from decoder: Decoder) throws {
let container:KeyedDecodingContainer = try decoder.container(keyedBy: CodingKeys.self)
let nameArray = try container.decode([String].self, forKey: .name)
let familyNameArray = try container.decode([String].self, forKey: .familyName)
self.name = nameArray.first!
self.familyName = familyNameArray.first!
}
enum CodingKeys: String, CodingKey {
case name
case familyName
}
}
let data = json.data(using: .utf8)!
let decodedDictionary = try JSONDecoder().decode(Dictionary<String, User>.self, from: data)
print(decodedDictionary) // ["user": __lldb_expr_48.User(name: "John", familyName: "Johnson")]
let encodedData = try JSONEncoder().encode(decodedDictionary["user"]!)
let encodedStr = String(data: encodedData, encoding: .utf8)
print(encodedStr!) // {"name":"John","familyName":"Johnson"}
My tendency would be to adapt your model to the data coming in and create computed properties for use in the application, e.g.
struct User: Codable {
var user: [String]
var familyName: [String]
var userFirstName: String? {
return user.first
}
var userFamilyName: String? {
return familyName.first
}
}
This allows you to easily maintain parody with the data structure coming in without the maintenance cost of overriding the coding/decoding.
If it goes well with your design, you could also have a UI wrapper Type or ViewModel to more clearly differentiate the underlying Model from it's display.

Resources