I have the following structure and response parsing fails because url (https://example.url.com/test a) has a space in it. How can I escape it with %20 value in deserialization layer and keep it as URL type?
struct Response: Decodable {
let url: URL
enum CodingKeys: String, CodingKey {
case url
}
init(from decoder: Decoder) throws {
self.url = try decoder.container(keyedBy: CodingKeys.self).decode(URL.self, forKey: .url)
}
}
let string = "{\"url\":\"https://example.url.com/test a\"}"
let responseModel = try? JSONDecoder().decode(Response.self, from: string.data(using: .utf8)!)
print(responseModel?.url)
I don't think that's possible with a JSONDecoder customisation or in the serialization layer as you've mentioned in the post. The best you can achieve would be to do this:
struct Response: Decodable {
let urlString: String
var url: URL {
URL(string: urlString.replacingOccurrences(of: " ", with: "%20"))!
}
enum CodingKeys: String, CodingKey {
case urlString = "url"
}
}
Note: You don't need init(decoder:) if you don't have a custom implementation. Also don't need the CodingKeys enum if the property names are the same as the string keys (In your case url key is redundant).
As Rob has already mentioned it would be better to fix the issue at the backend instead of fixing the decoding part. If you can not fix it at the server side you can decote your url as a string, percent escape it and then use the resulting string to initialize your url property:
struct Response: Codable {
let url: URL
}
extension Response {
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let string = try container.decode(String.self, forKey: .url)
guard
let percentEncoded = string
.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed),
let url = URL(string: percentEncoded)
else {
throw DecodingError.dataCorruptedError(forKey: .url,
in: container,
debugDescription: "Invalid url string: \(string)")
}
self.url = url
}
}
Playground testing
let jsonString = #"{"url":"https://example.url.com/test a"}"#
do {
let responseModel = try JSONDecoder().decode(Response.self, from: Data(jsonString.utf8))
print(responseModel.url)
} catch { print(error) }
This will print
https://example.url.com/test%20a
You cannot decode url as an URL, as in decode(URL.self...), because it is not an URL. Change your url property to String, decode this value as String.self, and deal with turning the String into a URL later in the process.
Related
This is the main struct that encodes properly as far as when I print print(String(data: encoded, encoding: .utf8) as Any)
the struct prints encoded with mainObject printing first then the rest of the variables in the struct but i want it to print: mainAccount, mainObject, reference in that order.
struct T: Codable {
init(){}
let mainAccount = "IMHOTECHPECOM"
let mainObject = mainObject()
let reference = UUID()
// Enum that allows easy encoding
enum CodingKeys: CodingKey {
case mainAccount, mainObject, reference;
}
// function to conform to encodable protocol
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(mainAccount, forKey: .merchantAccount)
try container.encode(mainObject.self, forKey: .mainObject)
try container.encode(reference, forKey: .reference)
}
// conforms with decodable protocol
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
_ = try container.decode(String.self, forKey: .mainAccount)
_ = try container.decode(String.self, forKey: .mainObject)
_ = try container.decode(String.self, forKey: .reference)
}
}
This is the mainObject
struct mainObject: Codable {
var type = "kind"
var identifier: String = ""
var guide: String = ""
init(){}
enum CodingKeys: CodingKey{
case type, identifier, guide;
}
// function to conform to encodable protocol
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(type, forKey: .type)
try container.encode(identifier, forKey: .identifier)
try container.encode(guide, forKey: .guide)
}
// conforms with decodable protocol
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
type = try container.decode(String.self, forKey: .type)
identifier = try container.decode(String.self, forKey: .identifier)
guide = try container.decode(String.self, forKey: .identifier)
}
}
This is the function in the actual view encoding the data from a button press
func getBalance() async {
let encoder = JSONEncoder()
encoder.outputFormatting = [.sortedKeys]
encoder.outputFormatting = [.withoutEscapingSlashes]
encoder.outputFormatting = [.prettyPrinted]
guard let encoded = try? encoder.encode(mainobject) else {
print("Failed to encode")
return
}
let url = URL(string: "https://testurl.com")!
var request = URLRequest(url: url)
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.httpMethod = "POST"
do {
let (response, _) = try await URLSession.shared.upload(for: request, from: encoded)
print(String(data: encoded, encoding: .utf8) as Any)
print(String(data: response, encoding: .utf8) as Any)
} catch {
print("Encoding failed")
}
let _ = try? JSONDecoder().decode(mainObject.self, from: encoded)
In this particular case, the order you're asking for happens to match alphabetical order. So if you add .sortedKeys to your encoder's outputFormatting property, you'll get the order you want:
let encoder = JSONEncoder()
encoder.outputFormatting = [.sortedKeys]
let data = try encoder.encode(myT)
This will affect the order of keys in all objects in the JSON. So your T's keys will be output in the order mainAccount, mainObject, reference, and your mainObject's keys will be output in the order guide, identifier, type.
The general answer is that JSONEncoder doesn't remember the order in which you add keys to a keyed container. Internally, it uses a standard Swift Dictionary to store the keys and values. A Swift Dictionary doesn't guarantee any ordering of its keys, and the order can change each time your program is started.
If you want to guarantee that the order of your keys is preserved, you'll have to write your own Encoder implementation, which is not a trivial task.
I am attempting to decode a JSON response from a third-party API which contains nested/child JSON that has been base64 encoded.
Contrived Example JSON
{
"id": 1234,
"attributes": "eyAibmFtZSI6ICJzb21lLXZhbHVlIiB9",
}
PS "eyAibmFtZSI6ICJzb21lLXZhbHVlIiB9" is { 'name': 'some-value' } base64 encoded.
I have some code that is able to decode this at present but unfortunately I have to reinstanciate an additional JSONDecoder() inside of the init in order to do so, and this is not cool...
Contrived Example Code
struct Attributes: Decodable {
let name: String
}
struct Model: Decodable {
let id: Int64
let attributes: Attributes
private enum CodingKeys: String, CodingKey {
case id
case attributes
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.id = try container.decode(Int64.self, forKey: .id)
let encodedAttributesString = try container.decode(String.self, forKey: .attributes)
guard let attributesData = Data(base64Encoded: encodedAttributesString) else {
fatalError()
}
// HERE IS WHERE I NEED HELP
self.attributes = try JSONDecoder().decode(Attributes.self, from: attributesData)
}
}
Is there anyway to achieve the decoding without instanciating the additional JSONDecoder?
PS: I have no control over the response format and it cannot be changed.
If attributes contains only one key value pair this is the simple solution.
It decodes the base64 encoded string directly as Data – this is possible with the .base64 data decoding strategy – and deserializes it with traditional JSONSerialization. The value is assigned to a member name in the Model struct.
If the base64 encoded string cannot be decoded a DecodingError will be thrown
let jsonString = """
{
"id": 1234,
"attributes": "eyAibmFtZSI6ICJzb21lLXZhbHVlIiB9",
}
"""
struct Model: Decodable {
let id: Int64
let name: String
private enum CodingKeys: String, CodingKey {
case id, attributes
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.id = try container.decode(Int64.self, forKey: .id)
let attributeData = try container.decode(Data.self, forKey: .attributes)
guard let attributes = try JSONSerialization.jsonObject(with: attributeData) as? [String:String],
let attributeName = attributes["name"] else { throw DecodingError.dataCorruptedError(forKey: .attributes, in: container, debugDescription: "Attributes isn't eiter a dicionary or has no key name") }
self.name = attributeName
}
}
let data = Data(jsonString.utf8)
do {
let decoder = JSONDecoder()
decoder.dataDecodingStrategy = .base64
let result = try decoder.decode(Model.self, from: data)
print(result)
} catch {
print(error)
}
I find the question interesting, so here is a possible solution which would be to give the main decoder an additional one in its userInfo:
extension CodingUserInfoKey {
static let additionalDecoder = CodingUserInfoKey(rawValue: "AdditionalDecoder")!
}
var decoder = JSONDecoder()
let additionalDecoder = JSONDecoder() //here you can put the same one, you can add different options, same ones, etc.
decoder.userInfo = [CodingUserInfoKey.additionalDecoder: additionalDecoder]
Because the main method we use from JSONDecoder() is func decode<T>(_ type: T.Type, from data: Data) throws -> T where T : Decodable and I wanted to keep it as such, I created a protocol:
protocol BasicDecoder {
func decode<T>(_ type: T.Type, from data: Data) throws -> T where T : Decodable
}
extension JSONDecoder: BasicDecoder {}
And I made JSONDecoder respects it (and since it already does...)
Now, to play a little and check what could be done, I created a custom one, in the idea of having like you said a XML Decoder, it's basic, and it's just for the fun (ie: do no replicate this at home ^^):
struct CustomWithJSONSerialization: BasicDecoder {
func decode<T>(_ type: T.Type, from data: Data) throws -> T where T : Decodable {
guard let dict = try JSONSerialization.jsonObject(with: data) as? [String: Any] else { fatalError() }
return Attributes(name: dict["name"] as! String) as! T
}
}
So, init(from:):
guard let attributesData = Data(base64Encoded: encodedAttributesString) else { fatalError() }
guard let additionalDecoder = decoder.userInfo[.additionalDecoder] as? BasicDecoder else { fatalError() }
self.attributes = try additionalDecoder.decode(Attributes.self, from: attributesData)
Let's try it now!
var decoder = JSONDecoder()
let additionalDecoder = JSONDecoder()
decoder.userInfo = [CodingUserInfoKey.additionalDecoder: additionalDecoder]
var decoder2 = JSONDecoder()
let additionalDecoder2 = CustomWithJSONSerialization()
decoder2.userInfo = [CodingUserInfoKey.additionalDecoder: additionalDecoder]
let jsonStr = """
{
"id": 1234,
"attributes": "eyAibmFtZSI6ICJzb21lLXZhbHVlIiB9",
}
"""
let jsonData = jsonStr.data(using: .utf8)!
do {
let value = try decoder.decode(Model.self, from: jsonData)
print("1: \(value)")
let value2 = try decoder2.decode(Model.self, from: jsonData)
print("2: \(value2)")
}
catch {
print("Error: \(error)")
}
Output:
$> 1: Model(id: 1234, attributes: Quick.Attributes(name: "some-value"))
$> 2: Model(id: 1234, attributes: Quick.Attributes(name: "some-value"))
After reading this interesting post, I came up with a reusable solution.
You can create a new NestedJSONDecodable protocol which gets also the JSONDecoder in it's initializer:
protocol NestedJSONDecodable: Decodable {
init(from decoder: Decoder, using nestedDecoder: JSONDecoder) throws
}
Implement the decoder extraction technique (from the aforementioned post) together with a new decode(_:from:) function for decoding NestedJSONDecodable types:
protocol DecoderExtractable {
func decoder(for data: Data) throws -> Decoder
}
extension JSONDecoder: DecoderExtractable {
struct DecoderExtractor: Decodable {
let decoder: Decoder
init(from decoder: Decoder) throws {
self.decoder = decoder
}
}
func decoder(for data: Data) throws -> Decoder {
return try decode(DecoderExtractor.self, from: data).decoder
}
func decode<T: NestedJSONDecodable>(_ type: T.Type, from data: Data) throws -> T {
return try T(from: try decoder(for: data), using: self)
}
}
And change your Model struct to conform to NestedJSONDecodable protocol instead of Decodable:
struct Model: NestedJSONDecodable {
let id: Int64
let attributes: Attributes
private enum CodingKeys: String, CodingKey {
case id
case attributes
}
init(from decoder: Decoder, using nestedDecoder: JSONDecoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.id = try container.decode(Int64.self, forKey: .id)
let attributesData = try container.decode(Data.self, forKey: .attributes)
self.attributes = try nestedDecoder.decode(Attributes.self, from: attributesData)
}
}
The rest of your code will remain the same.
You could create a single decoder as a static property of Model, configure it once, and use it for all your Model decoding needs, both externally and internally.
Unsolicited thought:
Honestly, I would only recommend doing that if you're seeing a measurable loss of CPU time or crazy heap growth from the allocation of additional JSONDecoders… they're not heavyweight objects, less than 128 bytes unless there's some trickery I don't understand (which is pretty common though tbh):
let decoder = JSONDecoder()
malloc_size(Unmanaged.passRetained(decoder).toOpaque()) // 128
I usually use structs that inherit from Decodable to serialize JSON data i'm pulling from my backend. However, I have an application where a JSON payload is identical to an existing struct with a few minor differences. Thus, I would like to have a decodable struct that inherits from another decodable struct like this:
class Party: Decodable {
var theme: String
}
class PublicParty : Party {
var address: String = ""
}
However, when I send the following JSON payload, only "theme" from the Party class gets decoded while the "address" from PublicParty remains "":
{
"theme": "Black out or get out",
"address": "404 Mundus, Tabasko Beach 45778 WA"
}
Swift code:
func test(completion: #escaping(Result<PublicParty, Error>) -> ()) {
guard let url = URL(string: "127.0.0.1:8000/test") else { return }
URLSession.shared.dataTask(with: url) { (data, response, error) in
// handle error
if let error = error {
completion(.failure(error))
return
}
// decode data
do {
let search = try JSONDecoder().decode(PublicParty.self, from: data!)
print(search)
completion(.success(search))
} catch let error {
completion(.failure(error))
}
}.resume()
}
As nothing is off in my front end/back end, does Swift even allow this functionality?
This worked:
class Party: Decodable {
var theme: String
}
class PublicParty : Party {
var address: String = ""
required init(from decoder: Decoder) throws {
try super.init(from: decoder)
let values = try decoder.container(keyedBy: CodingKeys.self)
address = try values.decode(String.self, forKey: .address)
}
private enum CodingKeys: String, CodingKey
{
case address
}
}
I have Codables running now. But the API has some String entries that can sometimes have an Int value of 0 if they are empty. I was searching here and found this: Swift 4 Codable - Bool or String values But I'm not able to get it running
My struct
struct check : Codable {
let test : Int
let rating : String?
}
Rating is most of the time something like "1Star". But if there is no rating I get 0 as Int back.
I'm parsing the data like this:
enum Result<Value> {
case success(Value)
case failure(Error)
}
func checkStar(for userId: Int, completion: ((Result<check>) -> Void)?) {
var urlComponents = URLComponents()
urlComponents.scheme = "https"
urlComponents.host = "xyz.com"
urlComponents.path = "/api/stars"
let userIdItem = URLQueryItem(name: "userId", value: "\(userId)")
urlComponents.queryItems = [userIdItem]
guard let url = urlComponents.url else { fatalError("Could not create URL from components") }
var request = URLRequest(url: url)
request.httpMethod = "GET"
let config = URLSessionConfiguration.default
config.httpAdditionalHeaders = [
"Authorization": "Bearer \(keytoken)"
]
let session = URLSession(configuration: config)
let task = session.dataTask(with: request) { (responseData, response, responseError) in
DispatchQueue.main.async {
if let error = responseError {
completion?(.failure(error))
} else if let jsonData = responseData {
// Now we have jsonData, Data representation of the JSON returned to us
// from our URLRequest...
// Create an instance of JSONDecoder to decode the JSON data to our
// Codable struct
let decoder = JSONDecoder()
do {
// We would use Post.self for JSON representing a single Post
// object, and [Post].self for JSON representing an array of
// Post objects
let posts = try decoder.decode(check.self, from: jsonData)
completion?(.success(posts))
} catch {
completion?(.failure(error))
}
} else {
let error = NSError(domain: "", code: 0, userInfo: [NSLocalizedDescriptionKey : "Data was not retrieved from request"]) as Error
completion?(.failure(error))
}
}
}
task.resume()
}
Loading it:
func loadStars() {
checkStar(for: 1) { (result) in
switch result {
case .success(let goo):
dump(goo)
case .failure(let error):
fatalError(error.localizedDescription)
}
}
}
I hope someone can help me there, cause I'm not completely sure how this parsing, etc. works.
you may implement your own decode init method, get each class property from decode container, during this section, make your logic dealing with wether "rating" is an Int or String, sign all required class properties at last.
here is a simple demo i made:
class Demo: Decodable {
var test = 0
var rating: String?
enum CodingKeys: String, CodingKey {
case test
case rating
}
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let test = try container.decode(Int.self, forKey: .test)
let ratingString = try? container.decode(String.self, forKey: .rating)
let ratingInt = try? container.decode(Int.self, forKey: .rating)
self.rating = ratingString ?? (ratingInt == 0 ? "rating is nil or 0" : "rating is integer but not 0")
self.test = test
}
}
let jsonDecoder = JSONDecoder()
let result = try! jsonDecoder.decode(Demo.self, from: YOUR-JSON-DATA)
if rating API's value is normal string, you will get it as you wish.
if rating API's value is 0, rating will equal to "rating is nil or 0"
if rating API's value is other integers, rating will be "rating is integer but not 0"
you may modify decoded "rating" result, that should be easy.
hope this could give you a little help. :)
for more info: Apple's encoding and decoding doc
I am retrieving a URL from a JSON parse, and upon doing that I would like to convert that URL into a UIImage. Here is my current data struct:
struct Book: Decodable {
let author: String
let artworkURL: URL
let genres: [Genre]
let name: String
let releaseDate: String
}
I tried to do:
struct Book: Decodable {
let author: String
var bookArtwork: UIImage
let artworkURL: URL {
didSet {
do {
if let imageData: Data = try Data(contentsOf: artworkURL) {
bookArtwork = UIImage(data: imageData)!
}
} catch {
print(error)
}
}
let genres: [Genre]
let name: String
let releaseDate: String
}
But that does not conform to protocol 'Decodable'
Anyone else have a solution?
Below I will enumerate a few ways of handling this sort of scenario, but the right answer is that you simply should not be retrieving images during the parsing of the JSON. And you definitely shouldn't be doing this synchronously (which is what Data(contentsOf:) does).
Instead, you should should only retrieve the images as they're needed by the UI. And you want to retrieve images into something that can be purged in low memory scenarios, responding to the .UIApplicationDidReceiveMemoryWarning system notification. Bottom line, images can take a surprising amount of memory if you're not careful and you almost always want to decouple the images from the model objects themselves.
But, if you're absolutely determined to incorporate the image into the Book object (which, again, I'd advise against), you can either:
You can make the bookArtwork a lazy variable:
struct Book: Decodable {
let author: String
lazy var bookArtwork: UIImage = {
let data = try! Data(contentsOf: artworkURL)
return UIImage(data: data)!
}()
let artworkURL: URL
let genres: [Genre]
let name: String
let releaseDate: String
}
This is horrible pattern in this case (again, because we should never do synchronous network call), but it illustrates the idea of a lazy variable. You sometimes do this sort of pattern with computed properties, too.
Just as bad (for the same reason, that synchronous network calls are evil), you can also implement a custom init method:
struct Book: Decodable {
let author: String
let bookArtwork: UIImage
let artworkURL: URL
let genres: [Genre]
let name: String
let releaseDate: String
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
author = try values.decode(String.self, forKey: .author)
artworkURL = try values.decode(URL.self, forKey: .artworkURL)
genres = try values.decode([Genre].self, forKey: .genres)
name = try values.decode(String.self, forKey: .name)
releaseDate = try values.decode(String.self, forKey: .releaseDate)
// special processing for artworkURL
let data = try Data(contentsOf: artworkURL)
bookArtwork = UIImage(data: data)!
}
enum CodingKeys: String, CodingKey {
case author, artworkURL, genres, name, releaseDate
}
}
In fact, this is even worse than the prior pattern, because at least with the first option, the synchronous network calls are deferred until when you reference the UIImage property.
But for more information on this general pattern see Encode and Decode Manually in Encoding and Decoding Custom Types.
I provide these two above patterns, as examples of how you can have some property not part of the JSON, but initialized in some other manner. But because you are using Data(contentsOf:), neither of these are really advisable. But they're good patterns to be aware of in case the property in question didn't require some time consuming synchronous task, like you do here.
In this case, I think it's simplest to just provide a method to retrieve the image asynchronously when you need it:
Eliminate the synchronous network call altogether and just provide an asynchronous method to retrieve the image:
struct Book: Decodable {
let author: String
let artworkURL: URL
let genres: [Genre]
let name: String
let releaseDate: String
func retrieveImage(completionHandler: #escaping (UIImage?, Error?) -> Void) {
let task = URLSession.shared.dataTask(with: artworkURL) { data, _, error in
guard let data = data, error == nil else {
completionHandler(nil, error)
return
}
completionHandler(UIImage(data: data), nil)
}
task.resume()
}
}
Then, when you need the image for your UI, you can retrieve it lazily:
book.retrieveImage { image, error in
DispatchQueue.main.async { image, error in
cell.imageView.image = image
}
}
Those are a few approaches you can adopt.