What Is Difference Between Optional and Decodeifpresent When Using Decodable For Json Parsing

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.

JSON Parsing using Decodable

This should work. I've removed Admin model for simplicity. I'd prefer Owners/Members to be arrays as they can have one or more values which is what they're for, but if you want them to be AnyObject, you can cast them as so like you're already doing in your init(decoder:).

Test data:

var json = """
{
"roomName": "6f9259d5-62d0-3476-6601-8c284a0b7dde",
"owners": {
"owner": "anish@local.mac"
},
"admins": null,
"members": {
"member": [
"steve@local.mac",
"mahe@local.mac"
]
}
}
""".data(using: .utf8)

Models:

struct ChatRoom: Codable, CustomStringConvertible {
var roomName: String! = ""
var owners: Owners? = nil
var members: Members? = nil

var description: String {
let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
let data = try? encoder.encode(self)
return String(data: data!, encoding: .utf8)!
}

enum RoomKeys: String, CodingKey {
case roomName
case owners
case members
case admins
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: RoomKeys.self)
roomName = try container.decode(String.self, forKey: .roomName)
members = try container.decode(Members.self, forKey: .members)
owners = try? container.decode(Owners.self, forKey: .owners)
}
}

struct Owners:Codable{
var owner: [String]?

enum OwnerKeys:String,CodingKey {
case owner
}

init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: OwnerKeys.self)
if let ownerValue = try? container.decode([String].self, forKey: .owner){
owner = ownerValue
}
else if let own = try? container.decode(String.self, forKey: .owner) {
owner = [own]
}
}
}

struct Members: Codable {
var member:[String]?

enum MemberKeys:String,CodingKey {
case member
}

init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: MemberKeys.self)
if let memberValue = try? container.decode([String].self, forKey: .member){
member = memberValue
}
else if let str = try? container.decode(String.self, forKey: .member){
member = [str]
}
}
}

Test:

var decoder = JSONDecoder()
try? print("\(decoder.decode(ChatRoom.self, from: json!))")

Output:

{
"owners" : {
"owner" : [
"anish@local.mac"
]
},
"members" : {
"member" : [
"steve@local.mac",
"mahe@local.mac"
]
},
"roomName" : "6f9259d5-62d0-3476-6601-8c284a0b7dde"
}

How can I decode two different response JSON in one request?

The "data corrupted" error seems to be due to the presence of the \n in the textSnippet. If we take care of that manually, I can't reproduce any issue. Here is a complete (unlike your code) example that compiles and runs correctly in an iOS project, using the JSON you provided (patched up to be valid JSON):

let json1 = """
{
"kind": "books#volumes",
"totalItems": 1196,
"items": [
{
"kind": "books#volume",
"id": "4qnmsgEACAAJ",
"etag": "dYXW8bT6JB0",
"selfLink": "https://www.googleapis.com/books/v1/volumes/4qnmsgEACAAJ",
"volumeInfo": {
"title": "Sineklerin Tanrisi",
"authors": [
"William Golding"
],
"publishedDate": "2014-01-01",
"industryIdentifiers": [
{
"type": "ISBN_10",
"identifier": "9754582904"
},
{
"type": "ISBN_13",
"identifier": "9789754582901"
}
],
"readingModes": {
"text": false,
"image": false
},
"pageCount": 261,
"printType": "BOOK",
"categories": [
"Boys"
],
"maturityRating": "NOT_MATURE",
"allowAnonLogging": false,
"contentVersion": "preview-1.0.0",
"imageLinks": {
"smallThumbnail": "http://books.google.com/books/content?id=4qnmsgEACAAJ&printsec=frontcover&img=1&zoom=5&source=gbs_api",
"thumbnail": "http://books.google.com/books/content?id=4qnmsgEACAAJ&printsec=frontcover&img=1&zoom=1&source=gbs_api"
},
"language": "tr",
"previewLink": "http://books.google.com.tr/books?id=4qnmsgEACAAJ&dq=sineklerin+tanrisi&hl=&cd=1&source=gbs_api",
"infoLink": "http://books.google.com.tr/books?id=4qnmsgEACAAJ&dq=sineklerin+tanrisi&hl=&source=gbs_api",
"canonicalVolumeLink": "https://books.google.com/books/about/Sineklerin_Tanrisi.html?hl=&id=4qnmsgEACAAJ"
},
"saleInfo": {
"country": "TR",
"saleability": "NOT_FOR_SALE",
"isEbook": false
},
"accessInfo": {
"country": "TR",
"viewability": "NO_PAGES",
"embeddable": false,
"publicDomain": false,
"textToSpeechPermission": "ALLOWED",
"epub": {
"isAvailable": false
},
"pdf": {
"isAvailable": false
},
"webReaderLink": "http://play.google.com/books/reader?id=4qnmsgEACAAJ&hl=&printsec=frontcover&source=gbs_api",
"accessViewStatus": "NONE",
"quoteSharingAllowed": false
}
}
]
}
"""

let json2 = """
{
"kind": "books#volumes",
"totalItems": 432,
"items": [
{
"kind": "books#volume",
"id": "yodWha1LmPsC",
"etag": "2bZz2CDqiq4",
"selfLink": "https://www.googleapis.com/books/v1/volumes/yodWha1LmPsC",
"volumeInfo": {
"title": "Momo the Monkey Arrives",
"authors": [
"Shariffa Keshavjee"
],
"publisher": "Master Publishing",
"publishedDate": "2012-09-21",
"description": "Momo the Monkey Arrives is the first in a series of illustrated children's books about the rescue of a monkey and how his presence livens up the household of two children: a boy and a girl. Along the way, as they take care of Momo, Geno and Alid learn how to give the best possible care to a monkey like him, and how to be more responsible children. The adventures of Momo are based on a true story.",
"industryIdentifiers": [
{
"type": "ISBN_13",
"identifier": "9789966158987"
},
{
"type": "ISBN_10",
"identifier": "9966158987"
}
],
"readingModes": {
"text": true,
"image": true
},
"pageCount": 28,
"printType": "BOOK",
"categories": [
"Juvenile Fiction"
],
"maturityRating": "NOT_MATURE",
"allowAnonLogging": false,
"contentVersion": "1.4.4.0.preview.3",
"panelizationSummary": {
"containsEpubBubbles": false,
"containsImageBubbles": false
},
"imageLinks": {
"smallThumbnail": "http://books.google.com/books/content?id=yodWha1LmPsC&printsec=frontcover&img=1&zoom=5&edge=curl&source=gbs_api",
"thumbnail": "http://books.google.com/books/content?id=yodWha1LmPsC&printsec=frontcover&img=1&zoom=1&edge=curl&source=gbs_api"
},
"language": "en",
"previewLink": "http://books.google.com.tr/books?id=yodWha1LmPsC&pg=PT25&dq=momo&hl=&cd=1&source=gbs_api",
"infoLink": "https://play.google.com/store/books/details?id=yodWha1LmPsC&source=gbs_api",
"canonicalVolumeLink": "https://play.google.com/store/books/details?id=yodWha1LmPsC"
},
"saleInfo": {
"country": "TR",
"saleability": "FOR_SALE",
"isEbook": true,
"listPrice": {
"amount": 35.04,
"currencyCode": "TRY"
},
"retailPrice": {
"amount": 35.04,
"currencyCode": "TRY"
},
"buyLink": "https://play.google.com/store/books/details?id=yodWha1LmPsC&rdid=book-yodWha1LmPsC&rdot=1&source=gbs_api",
"offers": [
{
"finskyOfferType": 1,
"listPrice": {
"amountInMicros": 35040000,
"currencyCode": "TRY"
},
"retailPrice": {
"amountInMicros": 35040000,
"currencyCode": "TRY"
}
}
]
},
"accessInfo": {
"country": "TR",
"viewability": "PARTIAL",
"embeddable": true,
"publicDomain": false,
"textToSpeechPermission": "ALLOWED",
"epub": {
"isAvailable": true
},
"pdf": {
"isAvailable": true
},
"webReaderLink": "http://play.google.com/books/reader?id=yodWha1LmPsC&hl=&printsec=frontcover&source=gbs_api",
"accessViewStatus": "SAMPLE",
"quoteSharingAllowed": false
},
"searchInfo": {
"textSnippet": "<b>Momo</b> the Monkey Adventure Series <b>Momo</b> the Monkey Arrives <b>Momo</b> Makes a <br>\\nMess <b>Momo</b> Comes to the Rescue <b>Momo</b> Moves to the Orphanage Look out for <br>\\nthese stories about <b>Momo</b> and his friends. More coming soon! Master Publishing<br>\\n ..."
}
}
]
}
"""

struct Base : Codable {
let kind : String
let totalItems : Int
let items : [Items]
}
struct Items : Codable {
let kind : String
let id : String
let etag : String
let selfLink : String
let volumeInfo : VolumeInfo
let saleInfo : SaleInfo?
let accessInfo : AccessInfo
let searchInfo : SearchInfo?
}
struct SaleInfo : Codable {
let country : String
let saleability : String
let isEbook : Bool
let listPrice : ListPrice?
let retailPrice : RetailPrice?
let buyLink : String?
let offers : [Offers]?
}
struct Offers : Codable {
let finskyOfferType : Int
let listPrice : ListPrice
let retailPrice : RetailPrice
}
struct ListPrice : Codable {
let amountInMicros : Int?
let currencyCode : String
}
struct ReadingModes : Codable {
let text : Bool
let image : Bool
}
struct Epub : Codable {
let isAvailable : Bool
}
struct Pdf : Codable {
let isAvailable : Bool
}
struct RetailPrice : Codable {
let amountInMicros : Int?
let currencyCode : String
}
struct SearchInfo : Codable {
let textSnippet : String
}
struct ImageLinks : Codable {
let smallThumbnail : String
let thumbnail : String
}
struct IndustryIdentifiers : Codable {
let type : String
let identifier : String
}
struct PanelizationSummary : Codable {
let containsEpubBubbles : Bool
let containsImageBubbles : Bool
}
struct VolumeInfo : Codable {
let title : String
let authors : [String]
let publisher : String?
let publishedDate : String
let description : String?
let industryIdentifiers : [IndustryIdentifiers]
let readingModes : ReadingModes
let pageCount : Int
let printType : String
let categories : [String]
let maturityRating : String
let allowAnonLogging : Bool
let contentVersion : String
let panelizationSummary : PanelizationSummary?
let imageLinks : ImageLinks
let language : String
let previewLink : String
let infoLink : String
let canonicalVolumeLink : String
}
struct AccessInfo : Codable {
let country : String
let viewability : String
let embeddable : Bool
let publicDomain : Bool
let textToSpeechPermission : String
let epub : Epub
let pdf : Pdf
let webReaderLink : String
let accessViewStatus : String
let quoteSharingAllowed : Bool
}


class ViewController: UIViewController {

override func viewDidLoad() {
super.viewDidLoad()

let data1 = json1.data(using: .utf8)!
do {
let result = try JSONDecoder().decode(Base.self, from: data1)
print("success1")
print(result)
} catch {
print(error)
}
let data2 = json2.data(using: .utf8)!
do {
let result = try JSONDecoder().decode(Base.self, from: data2)
print("success2")
print(result)
} catch {
print(error)
}


}


}

Swift Codable Decode Manually Optional Variable

Age is optional:

let age: String? 

So try to decode in this way:

let age: String? = try values.decodeIfPresent(String.self, forKey: .age)

Swift Combine version of decodeIfPresent?

Ideally, you can inspect the server response and decide what you want to do, say given a specific HTTP code or if data is empty, but in your case, retrieveData just gives you data - so, there isn't much to play with here.

What you could is attempt to decode, and if there's a failure, return nil:

return retrieveData(with: request)
.flatMap {
Just($0)
.decode(type: T?.self, decoder: decoder)
.replaceError(with: nil)
}
.mapError { ... }
//... etc

The downside of the above is that it would hide any actual decoding errors, like type mismatches, so you could be more precise in your handling and only decode when data is not empty.

Here's a possible decodeIfPresent implementation:

extension Publisher where Output == Data {
func decodeIfPresent<T: Decodable, Coder: TopLevelDecoder>(
type: T.Type,
decoder: Coder
) -> AnyPublisher<T?, Error> where Coder.Input == Output {

self.mapError { $0 as Error }
.flatMap { d -> AnyPublisher<T?, Error> in
if d.isEmpty {
return Just<T?>(nil)
.setFailureType(to: Error.self)
.eraseToAnyPublisher()
} else {
return Just(d)
.decode(type: T?.self, decoder: decoder)
.eraseToAnyPublisher()
}
}
.eraseToAnyPublisher()
}
}

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?
}

How to decode JSON Array with different objects with Codable in Swift?

What I want to achive is that one struct as a wrapper and struct for (Id, ServicePublicKey, Token etc..).
At the end when there is a new type coming from JSON, I need to write only relevant struct and add some code inside wrapper.
My Question is that: How can I parse this JSON without any optional variable?

First of all I totally agree with your idea.
When decoding a JSON we should always aim to

  • no optionals (as long as this is guaranteed by the backend)
  • easy extendibility

Let's start

So given this JSON

let data = """
{
"Response": [
{
"Id": {
"id": 123456
}
},
{
"Token": {
"token": "myToken",
"updated": "2020-01-11 13:55:43.397764",
"created": "2020-01-11 13:55:43.397764",
"id": 123456
}
},
{
"ServerPublicKey": {
"server_public_key": "some key"
}
}
]
}
""".data(using: .utf8)!

ID Model

struct ID: Decodable {
let id: Int
}

Token Model

struct Token: Decodable {
let token: String
let updated: String
let created: String
let id: Int
}

ServerPublicKey Model

struct ServerPublicKey: Decodable {
let serverPublicKey: String
enum CodingKeys: String, CodingKey {
case serverPublicKey = "server_public_key"
}
}

Result Model

struct Result: Decodable {

let response: [Response]

enum CodingKeys: String, CodingKey {
case response = "Response"
}

enum Response: Decodable {

enum DecodingError: Error {
case wrongJSON
}

case id(ID)
case token(Token)
case serverPublicKey(ServerPublicKey)

enum CodingKeys: String, CodingKey {
case id = "Id"
case token = "Token"
case serverPublicKey = "ServerPublicKey"
}

init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
switch container.allKeys.first {
case .id:
let value = try container.decode(ID.self, forKey: .id)
self = .id(value)
case .token:
let value = try container.decode(Token.self, forKey: .token)
self = .token(value)
case .serverPublicKey:
let value = try container.decode(ServerPublicKey.self, forKey: .serverPublicKey)
self = .serverPublicKey(value)
case .none:
throw DecodingError.wrongJSON
}
}
}
}

Let's decode!

We can finally decode your JSON

do {
let result = try JSONDecoder().decode(Result.self, from: data)
print(result)
} catch {
print(error)
}

Output

And this is the output

Result(response: [
Result.Response.id(
Result.Response.ID(
id: 123456
)
),
Result.Response.token(
Result.Response.Token(
token: "myToken",
updated: "2020-01-11 13:55:43.397764",
created: "2020-01-11 13:55:43.397764",
id: 123456)
),
Result.Response.serverPublicKey(
Result.Response.ServerPublicKey(
serverPublicKey: "some key"
)
)
])

Date Decoding

I leave the date decoding to you as homework ;-)

UPDATE

This additional part should answer to your comment

Can we store variables like id, serverPublicKey inside Result struct
without Response array. I mean instead of ResponseArray can we just
have properties? I think It need a kind of mapping but I can't figure
out.

Yes, I think we can.

We need to add one more struct to the ones already described above.

Here it is

struct AccessibleResult {

let id: ID
let token: Token
let serverPublicKey: ServerPublicKey

init?(result: Result) {

typealias ComponentsType = (id: ID?, token: Token?, serverPublicKey: ServerPublicKey?)

let components = result.response.reduce(ComponentsType(nil, nil, nil)) { (res, response) in
var res = res
switch response {
case .id(let id): res.id = id
case .token(let token): res.token = token
case .serverPublicKey(let serverPublicKey): res.serverPublicKey = serverPublicKey
}
return res
}

guard
let id = components.id,
let token = components.token,
let serverPublicKey = components.serverPublicKey
else { return nil }

self.id = id
self.token = token
self.serverPublicKey = serverPublicKey
}
}

This AccessibleResult struct has an initialiser which receives a Result value and tries to populated its 3 properties

let id: ID
let token: Token
let serverPublicKey: ServerPublicKey

If everything goes fine, I mean if the input Result contains at least an ID, a Token and a ServerPublicKey then the AccessibleResponse is initialised, otherwise the init fails and nil` is returned.

Test

if
let result = try? JSONDecoder().decode(Result.self, from: data),
let accessibleResult = AccessibleResult(result: result) {
print(accessibleResult)
}

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.

Decode data of Object type and generic type for base JSON response using Decodable

You don't need a generic structure. Just create a optional property to assign your object in case there is no user array:



struct BaseModel {
let data: [User]
let post: Post?
let success: Bool
let message: String
}


struct User: Codable {
let id: Int
let name: String
}


struct Post: Codable {
let heading: String
let title: String
let content: String
}


extension BaseModel: Codable  {
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
do {
data = try container.decode([User].self, forKey: .data)
post = nil
} catch DecodingError.typeMismatch {
data = []
post = try container.decode(Post.self, forKey: .data)
}
success = try container.decode(Bool.self, forKey: .success)
message = try container.decode(String.self, forKey: .message)
}
}

If there is other responses not shown in your post you can do the same approach above using a generic structure as well:

struct BaseModel<T: Codable> {
let array: [T]
let element: T?
let success: Bool
let message: String
}


extension BaseModel: Codable  {
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
do {
array = try container.decode([T].self, forKey: .array)
element = nil
} catch DecodingError.typeMismatch {
array = []
element = try container.decode(T.self, forKey: .array)
}
success = try container.decode(Bool.self, forKey: .success)
message = try container.decode(String.self, forKey: .message)
}
}


Related Topics



Leave a reply



Submit