Swift Decodable Optional Key

Swift Decodable Optional Key

You can use the following KeyedDecodingContainer function:

func contains(_ key: KeyedDecodingContainer.Key) -> Bool

Returns a Bool value indicating whether the decoder contains a value associated with the given key. The value associated with the given key may be a null value as appropriate for the data format.

For instance, to check if the "age" key exists before requesting the corresponding nested container:

struct Person: Decodable {
let firstName, lastName: String
let age: Int?

enum CodingKeys: String, CodingKey {
case firstName = "firstname"
case lastName = "lastname"
case age
}

enum AgeKeys: String, CodingKey {
case realAge = "realage"
case fakeAge = "fakeage"
}

init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
self.firstName = try values.decode(String.self, forKey: .firstName)
self.lastName = try values.decode(String.self, forKey: .lastName)

if values.contains(.age) {
let age = try values.nestedContainer(keyedBy: AgeKeys.self, forKey: .age)
self.age = try age.decodeIfPresent(Int.self, forKey: .realAge)
} else {
self.age = nil
}
}
}

How can I use @propertyWrapper for Decodable with optional keys?

The synthesized code for init(from:) normally uses decodeIfPresent when the type is optional. However, property wrappers are always non-optional and only may use an optional as their underlying value. That's why the synthesizer always uses the normal decode which fails if the key isn't present (a good writeup in the Swift Forums).

I solved the problem by using the excellent CodableWrappers package:

public struct NonConformingBoolStaticDecoder: StaticDecoder {

public static func decode(from decoder: Decoder) throws -> Bool {
if let stringValue = try? String(from: decoder) {
switch stringValue.lowercased() {
case "false", "no", "0": return false
case "true", "yes", "1": return true
default:
throw DecodingError.valueNotFound(self, DecodingError.Context(
codingPath: decoder.codingPath,
debugDescription: "Expected true/false, yes/no or 0/1 but found \(stringValue) instead"))
}
} else {
return try Bool(from: decoder)
}
}
}

typealias NonConformingBoolDecoding = DecodingUses<NonConformingBoolStaticDecoder>

Then I can define my decodable struct like this:

public struct MyType: Decodable {
@OptionalDecoding<NonConformingBoolDecoding> var someKey: Bool?
}

Swift custom decodable initializer without CodingKeys

The auto-generation for CodingKeys is really weird. The scope and availability of it changes based on what members you have.

Say you just have a Decodable. These compile:

struct Decodable: Swift.Decodable {
static var codingKeysType: CodingKeys.Type { CodingKeys.self }
}
struct Decodable: Swift.Decodable {
static func `init`(from decoder: Decoder) throws -> Self {
_ = CodingKeys.self
return self.init()
}
}

…and you can put them together, if you add private.

struct Decodable: Swift.Decodable {
private static var codingKeysType: CodingKeys.Type { CodingKeys.self }

static func `init`(from decoder: Decoder) throws -> Self {
_ = CodingKeys.self
return self.init()
}
}

…But make that func an initializer, and again, no compilation.

struct Decodable: Swift.Decodable {
private static var codingKeysType: CodingKeys.Type { CodingKeys.self }

init(from decoder: Decoder) throws {
_ = CodingKeys.self
}
}

You can change it to be fully Codable, not just Decodable

struct Decodable: Codable {
private static var codingKeysType: CodingKeys.Type { CodingKeys.self }

init(from decoder: Decoder) throws {
_ = CodingKeys.self
}
}

But then you can't use CodingKeys at type scope, so the property won't compile.

Considering you probably don't need such a property, just use Codable, file a bug with Apple referencing this answer, and hopefully we can all switch to Decodable when they fix it. /p>

Decoding json with optional fields

You should split this into several steps in order to avoid to handle all these optionals in your model.

First create a struct that has only those properties that are guaranteed to be there. ok in your case:

struct OKResult: Codable{
let ok: Bool
}

then create one for your error state and one for your success state:

struct ErrorResult: Codable{
let ok: Bool
let errorCode: Int
let error: String

private enum CodingKeys: String, CodingKey{
case ok, errorCode = "error_code", error
}
}

struct ShortLinkData: Codable {
let ok: Bool
let result: Result
}

struct Result: Codable {
let code, shortLink: String
let fullShortLink: String
let shortLink2: String
let fullShortLink2: String
let shortLink3: String
let fullShortLink3: String
let shareLink: String
let fullShareLink: String
let originalLink: String

enum CodingKeys: String, CodingKey {
case code
case shortLink = "short_link"
case fullShortLink = "full_short_link"
case shortLink2 = "short_link2"
case fullShortLink2 = "full_short_link2"
case shortLink3 = "short_link3"
case fullShortLink3 = "full_short_link3"
case shareLink = "share_link"
case fullShareLink = "full_share_link"
case originalLink = "original_link"
}
}

Then you can decode the data:

guard try JSONDecoder().decode(OKResult.self, from: data).ok else{
let errorResponse = try JSONDecoder().decode(ErrorResult.self, from: data)
//handle error scenario
fatalError(errorResponse.error) // or throw custom error or return nil etc...
}

let shortlinkData = try JSONDecoder().decode(ShortLinkData.self, from: data)

Remarks:

  • Your inits are not necessary.
  • Never use try? this will hide all errors from you
  • you would need to wrap this either in a do catch block or make your function throwing and handle errors further up the tree.

Swift decodable with programatically provided coding keys

It's possible to provide any contextual information to the decoder with userInfo property and in this case we can pass an array of coding keys and use this info in the decoding process:

struct Info: Decodable {
var text: String?
var num: Int?

static var keys = CodingUserInfoKey(rawValue: "keys")!

enum CodingKeys: String, CodingKey {
case text, num
}

init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)

guard let keys = decoder.userInfo[Self.keys] as? [CodingKeys] else {
return
}

if keys.contains(.text) {
text = try container.decode(String.self, forKey: .text)
}

if keys.contains(.num) {
num = try container.decode(Int.self, forKey: .num)
}
}
}

struct Root: Decodable {
let info: Info
}

let json = #"{ "info" : { "text": "Hello", "num": 20 } }"#.data(using: .utf8)!

let decoder = JSONDecoder()
let keys: [Info.CodingKeys] = [.text]
decoder.userInfo[Info.keys] = keys
let root = try decoder.decode(Root.self, from: json)
print(root)

// Outputs:
Root(info: Info(text: Optional("Hello"), num: nil))

Swift 4 JSONDecoder optional variable

If you have local variables you have to specify the CodingKeys

public struct VIO: Codable {

private enum CodingKeys : String, CodingKey { case id }

let id:Int?
...
var par1:Bool = false
var par2:Bool = false

}

Edit:

If par1 and par2 should be also decoded optionally you have to write a custom initializer

  private enum CodingKeys : String, CodingKey { case id, par1, par2 }

init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
id = try container.decode(Int.self, forKey: .id)
par1 = try container.decodeIfPresent(Bool.self, forKey: .par1)
par2 = try container.decodeIfPresent(Bool.self, forKey: .par2)
}

This is Swift: No trailing semicolons

What is difference between optional and decodeIfPresent when using Decodable for JSON Parsing?

There's a subtle, but important difference between these two lines of code:

// Exhibit 1
foo = try container.decode(Int?.self, forKey: .foo)
// Exhibit 2
foo = try container.decodeIfPresent(Int.self, forKey: .foo)

Exhibit 1 will parse:

{
"foo": null,
"bar": "something"
}

but not:

{
"bar": "something"
}

while exhibit 2 will happily parse both. So in normal use cases for JSON parsers you'll want decodeIfPresent for every optional in your model.

General strategy to decode type mismatch keys in JSON into nil when optional in Swift

One approach would be to create a property wrapper that's Decodable to use for these these kind of properties:

@propertyWrapper
struct NilOnTypeMismatch<Value> {
var wrappedValue: Value?
}

extension NilOnTypeMismatch: Decodable where Value: Decodable {
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
self.wrappedValue = try? container.decode(Value.self)
}
}

Then you could selectively wrap the properties that you want to special-handle:

struct Foo : Decodable {
@NilOnTypeMismatch
var bar : Int?
}

A more holistic approach would be to extend KeyedDecodingContainer for Ints, but that would apply app-wide:

extension KeyedDecodingContainer {
func decodeIfPresent(_ type: Int.Type, forKey key: K) throws -> Int? {
try? decode(Int.self, forKey: key)
}
}

Unfortunately, I don't think it's possible (or don't know how) to make it generic, since my guess is that this function overload is at a lower priority than a default implementation when using generics.

Default enum value for optional key

You don't need to create a custom init(from:) method for Format, you only need one for SubStep. You need to use container.decodeIfPresent(_:forKey:) to decode a key which might not be present in your JSON, in which case it returns nil.

struct SubStep: Decodable {
enum Format: String, Decodable {
case bold
case regular
}

let format: SubStep.Format
let text: String

private enum CodingKeys: String, CodingKey {
case text, format
}

init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.text = try container.decode(String.self, forKey: .text)
self.format = try container.decodeIfPresent(Format.self, forKey: .format) ?? .regular
}
}

Unrelated to your issue, but you don't need to provide a String rawValue for your enum cases if their rawValue would exactly match the name of the case, the compiler will autosynthetise those for you.

With JSONDecoder in Swift 4, can missing keys use a default value instead of having to be optional properties?

Approach that I prefer is using so called DTOs - data transfer object.
It is a struct, that conforms to Codable and represents the desired object.

struct MyClassDTO: Codable {
let items: [String]?
let otherVar: Int?
}

Then you simply init the object that you want to use in the app with that DTO.

 class MyClass {
let items: [String]
var otherVar = 3
init(_ dto: MyClassDTO) {
items = dto.items ?? [String]()
otherVar = dto.otherVar ?? 3
}

var dto: MyClassDTO {
return MyClassDTO(items: items, otherVar: otherVar)
}
}

This approach is also good since you can rename and change final object however you wish to.
It is clear and requires less code than manual decoding.
Moreover, with this approach you can separate networking layer from other app.



Related Topics



Leave a reply



Submit