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
init
s 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 functionthrowing
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 Int
s, 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
How to Make a Mkannotationview Touch Sensitive
How to Get Assets.Xcassets File Names in an Array (Or Some Data Structure)
Apple MACh-O Linker (Id) Warning:Building for MACosx, But Linking Against Dylib Built for iOS
How to Sort Nsmutablearray of Date Objects
Wkwebview and Uimenucontroller
Get User Swiping Direction in Uipageviewcontroller
How to Connect SQLite Database in iOS
Make Navigationbar's Titleview Larger Than Itself
"Invalid Predicate: Nil Rhs" for Second Argument in Nspredicate Format
Learn About the Nsxmlparser in iOS
Facebook App Invites Notification Not Working in iOS
Ios7 - Device Unique Identifier
Uitableview Pagination - Bottom Refresh to Load New Data in Swift