Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Optional URLs causing JSON decoding to fail in Swift 4

I'm trying to figure out why using Optional URLs causes my JSON Decoding to fail using Swift 4. I've already poured over the WWDC "Whats new in Foundation" video, Apples Playground examples, and a bunch of places on the internet but haven't found a solution.

This is a test I created to show the issue. Say this is my json data:

let json = """
{
    "kind" : "",
    "total" : 2,
    "image_url" : "https://www.myawesomeimageURL.com/awesomeImage.jpg"
}
""".data(using: .utf8)!

And this is my struct:

    struct BusinessService: Decodable {
    let kind: String?
    let total: Int
    let image_url : URL?
}

And here's where I serialize it:

let myBusiness = try? JSONDecoder().decode(BusinessService.self, from: json)
print(myBusiness)

Now, the problem is if I receive json data where image_url is missing, the json decoding fails and gives me nil.

I am confused as to why "kind" can be an optional string and not cause the json decoding to fail when it has no value, yet image_url cannot be an optional URL without causing the json to fail.

I've been trying to figure this out for two days now with no luck. Does anyone have any insight into why this would fail?

I did notice one more piece of the puzzle, and that is the image_url in the json actually doesn't have to be a valid image URL. I can type in any string and my struct gets serialized so I think maybe I'm misunderstanding how this works. I thought that URL would automatically be populated only if it could convert to a valid URL and that isn't happening.

Any insight to this would be greatly appreciated.

like image 877
SN81 Avatar asked Sep 23 '17 17:09

SN81


2 Answers

This works for me as expected. Setting image_url to null or leaving it out completely leads to a valid BusinessService getting decoded with a nil value for the image_url field:

struct Example: Decodable {
    let url: URL?
}

// URL is present. This works, obviously.
try JSONDecoder().decode(Example.self, from: """
    { "url": "foo" }
    """.data(using: .utf8)!)

// URL is missing, indicated by null. Also works.
try JSONDecoder().decode(Example.self, from: """
    { "url": null }
    """.data(using: .utf8)!)

// URL is missing, not even the key is present. Also works.
try JSONDecoder().decode(Example.self, from: """
    {}
    """.data(using: .utf8)!) // works, too

The only trouble comes up when I supply an invalid URL value such as an empty string for the field. So I guess that’s your problem?

// This throws: "Invalid URL string."
try JSONDecoder().decode(Example.self, from: """
    { "url": "" }
    """.data(using: .utf8)!)

You have to send a “none” value for the URL field by sending null or leaving the whole line out, not send an empty string.

I am confused as to why "kind" can be an optional string and not cause the json decoding to fail when it has no value, yet image_url cannot be an optional URL without causing the json to fail.

The difference is that "" in JSON is a valid String value, but not a valid URL.

like image 59
zoul Avatar answered Nov 16 '22 16:11

zoul


There is one simple tweak we can implement to handle this case. You can use try catch block to decode url item. If there is Invalid URL Case, then we can assign null value in try catch block.

    struct Example: Codable {
    let url: URL?
    
    private enum CodingKeys: String, CodingKey {
      case url
    }
    
    init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        do {
            url = try container.decode(URL.self, forKey: .url)
        }catch( _) {
            url = nil
        }
    }
}

Note: In swift playground the above code some how still results in crash, but it just work as expected in regular Xcode project

like image 33
Rajan Twanabashu Avatar answered Nov 16 '22 15:11

Rajan Twanabashu