I am having problems with mapping down some json data that are coming from the API, A lot of errors are coming and I am really having a difficult time dealing with this one since I am new to swift.
Structure:
{
"StatusCode": 0,
"Result": [
{
"Type": "Test",
"Date": "Test",
"Message": "Test"
},
{
"Type": "Test",
"Date": "Test",
"Message": "Test"
}
]
}
My Structure That is not Working:
struct Notifications: Decodable {
struct NotificationsStructure: Decodable {
var Type: String?
var Date: String?
var Message: String?
}
var StatusCode: Int
var Result: NotificationsStructure!
}
First of all never declare a property in a Decodable object as implicit unwrapped optional. If it can be missing or be nil declare it as regular optional (?) otherwise non-optional.
In JSON there are only two collection types:
dictionary represented by {},
array represented by []
The value for key Result is clearly an array.
And add CodingKeys to map the capitalized keys to lowercase property names
struct Notifications: Decodable {
struct NotificationsStructure: Decodable {
private enum CodingKeys: String, CodingKey {
case type = "Type", date = "Date", message = "Message"
}
let type: String
let date: String
let message: String
}
private enum CodingKeys: String, CodingKey {
case statusCode = "StatusCode", result = "Result",
}
let statusCode: Int
let result: [NotificationsStructure]
}
The response from result is an array type . You should create another object that have same properties of each array.
struct NotificationResponse : Decodable {
let StatusCode : Int
let Result: [Notification]
}
struct Notification : Decodable {
let Type: String
let Date: String
let Message: String
}
Related
I have a JSON data structure representing Animals. e.g.:
{
"id": "3",
"type": "dog",
"properties": {
"tail_color": "black",
"paw_color": "white"
}
}
{
"id": "5",
"type": "fish",
"properties": {
"fin_color": "black",
"num_teeth": "3"
"max_speed": "27"
}
}
id and type are consistent for all animals but the keys within properties change depending on type.
I want to parse these animals into struct AnimalModel. Because properties is inconsistent, I want to store this sub-json as string (and process it later):
struct AnimalModel: Identifiable, Codable {
let id: String
let type: String
let properties: String // JSON, content and keys depending on type
enum CodingKeys: String, CodingKey {
case id
case type
case properties
}
}
// data .. JSON-string
guard let newAnimal = try? JSONDecoder().decode(AnimalModel.self, from: data) else {
return
}
The JSONDecoder() however fails here. How can I parse properties as string into AnimalModel?
I have a JSON response from my api that returns this:
[
{
"id": 1,
"chapter": 5,
"amount": 28,
"texts": [
{
"lyric": "lorem ipsum",
"number": 1
},
{
"lyric": "lorem foo bar",
"number": 2
}
],
"book": 1
}
]
I tried
struct Chapter: Decodable, Identifiable {
var id: Int
var chapter: Int
var amount: Int
struct Lyrics: Codable {
var lyricText: String
var lyricNumber: Int
}
enum Codingkeys: String, CodingKey {
case lyricText = "lyric"
case lyricNumber = "number"
}
}
But I get the following error upon making the call
dataCorrupted(Swift.DecodingError.Context(codingPath: [], debugDescription: "The given data was not valid JSON.", underlyingError: Optional(Error Domain=NSCocoaErrorDomain Code=3840 "Invalid value around character 0." UserInfo={NSDebugDescription=Invalid value around character 0.})))
My API call looks like this:
...
#Published var chapters = [Chapter]()
func fetchBookDetails() {
if let url = URL(string: url) {
let session = URLSession(configuration: .default)
let task = session.dataTask(with: url) { (data, response, error) in
if error == nil {
if let safeData = data {
do {
let response = try JSONDecoder().decode([Chapter].self, from: safeData)
DispatchQueue.main.async {
self.chapters = response
}
} catch {
print(error)
}
}
}
}
task.resume()
}
}
The struct looks fine I guess, but the api call is complaining - any idea what it could be? Or is it the struct that is done incorrectly
texts is a sub structure (an array of properties), so you need to define a second container for it, for example
struct Text: Codable {
let lyric: String
let number: Int
}
Then you can update Chapter to reference the sub structure something like...
struct Chapter: Decodable {
let id: Int
let chapter: Int
let amount: Int
let book: Int
let texts: [Text]
}
And finally, load it...
let chapters = try JSONDecoder().decode([Chapter].self, from: jsonData)
But what about the error message?
dataCorrupted(Swift.DecodingError.Context(codingPath: [], debugDescription: "The given data was not valid JSON.", underlyingError: Optional(Error Domain=NSCocoaErrorDomain Code=3840 "Invalid value around character 0." UserInfo={NSDebugDescription=Invalid value around character 0.})))
Oh, right, but the error message is telling there is something wrong with what you've downloaded. I like to, in these cases, convert the data to String and print it if possible, that way, you know what is been returned to you.
For example:
let actualText = String(data: safeData, encoding: .utf8)
The print this and see what you're actually getting
The Playground test code
import UIKit
let jsonText = """
[
{
"id": 1,
"chapter": 5,
"amount": 28,
"texts": [
{
"lyric": "lorem ipsum",
"number": 1
},
{
"lyric": "lorem foo bar",
"number": 2
},
],
"book": 1
}
]
"""
struct Text: Codable {
let lyric: String
let number: Int
}
struct Chapter: Decodable {
let id: Int
let chapter: Int
let amount: Int
let book: Int
let texts: [Text]
}
let jsonData = jsonText.data(using: .utf8)!
do {
let chapters = try JSONDecoder().decode([Chapter].self, from: jsonData)
} catch let error {
error
}
I'm having a problem to pass this Codable as parameter for my api service.
The value key has different types (String, Bool, Int). I always get String values of those whenever I pass this as my parameter :(
{
"deviceId": "aabbcc112233",
"commands": [
{
"code": "mode",
"value": "play"
},
{
"code": "start",
"value": false
},
{
"code": "timer",
"value": 4
}
],
"type": "activity"
}
My current code is this:
struct MyParameter: Codable {
var deviceId: String
var commands: [CommandStatus]
var type: String
enum CodingKeys: String, CodingKey {
case deviceId = "deviceId"
case commands = "commands"
case type = "type"
}
}
struct CommandStatus: Codable {
var code: String
var value: String
init(code: String, value: String) {
self.code = code
self.value = value
}
init(code: String, value: Int) {
self.code = code
self.value = String(value)
}
init(code: String, value: Bool) {
self.code = code
self.value = String(value)
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(value, forKey: .value)
try container.encode(value.boolValue, forKey: .value)
try container.encode((value as NSString).integerValue, forKey: .value)
}
enum CodingKeys: String, CodingKey {
case code, value
}
}
Can someone help me understand more on how to do this?
I have a problem with decoding JSON from API.
I get JSON:
{
"meta": {
"count": 1,
"links": {
"self": "https://test.api.amadeus.com/v1/reference-data/locations?subType=AIRPORT&keyword=barcel&sort=analytics.travelers.score&view=LIGHT&page%5Boffset%5D=0&page%5Blimit%5D=10"
}
},
"data": [
{
"type": "location",
"subType": "AIRPORT",
"name": "AIRPORT",
"detailedName": "BARCELONA/ES:AIRPORT",
"id": "ABCN",
"self": {
"href": "https://test.api.amadeus.com/v1/reference-data/locations/ABCN",
"methods": [
"GET"
]
},
"iataCode": "BCN",
"address": {
"cityName": "BARCELONA",
"countryName": "SPAIN"
}
}
]
}
My code looks like this:
struct Airport: Decodable {
let meta: MetaAirport
let data: AirportInfo
}
struct Address: Decodable{
let countryName: String
let cityName: String
}
struct AirportInfo: Decodable{
let address: Address
let subType: String
let id: String
let type: String
let detailedName: String
let name: String
let iataCode: String
}
struct MetaAirport : Decodable{
let count: Int
let links: Links
}
struct Links : Decodable{
let info: String
private enum CodingKeys : String, CodingKey {
case info = "self"
}
}
I'm trying to decode json using JSONDecoder on an AirportInfo object
do {
let airport = try JSONDecoder().decode(Airport.self, from: data!)
print(airport.data.name)
}
catch let err {
print(err)
}
I get an error
typeMismatch(Swift.Dictionary, Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: "data", intValue: nil)], debugDescription: "Expected to decode Dictionary but found an array instead.", underlyingError: nil))
What am I doing wrong?
In json data, address is a object. Change your code:
let address: [Address] -> let address: Address
Your Model should be like that to parse
struct DataModel: Codable {
let meta: Meta?
let data: [Datum]?
}
struct Datum: Codable {
let type, subType, name, detailedName: String?
let id: String?
let datumSelf: SelfClass?
let iataCode: String?
let address: Address?
enum CodingKeys: String, CodingKey {
case type, subType, name, detailedName, id
case datumSelf = "self"
case iataCode, address
}
}
struct Address: Codable {
let cityName, countryName: String?
}
struct SelfClass: Codable {
let href: String?
let methods: [String]?
}
struct Meta: Codable {
let count: Int?
let links: Links?
}
struct Links: Codable {
let linksSelf: String?
enum CodingKeys: String, CodingKey {
case linksSelf = "self"
}
}
I see you have edited your question from the original, so it feels hard to correctly identify at a given moment in time where the issue is.
However, Codable is great and one easy way for you to work through it is by constructing your object little by little. There is no requirement for an object to identically represent your JSON IF you make things optional. So when debugging this kind of issues, make different properties optional and see what got parsed. This would allow you to identify little by little where you're doing things wrong, without causing crashes and keeping the correctly formatted parts.
Also, pro tip: If you plan to keep this code for anything more than 1 week, stop using force unwrapping ! and get used to handle optionals correctly. Otherwise this is a hard habit to stop and others will hate the code you've created ;)
Consider this example JSON:
{
"sections": [{
"title": "Sign up",
"rows": [
{
"type": "image",
"imageURL": "https://example.com/image.jpg"
},
{
"type": "textField",
"value": "",
"placeholder": "Username"
},
{
"type": "textField",
"placeholder": "password"
},
{
"type": "textField",
"placeholder": "confirmPassword"
},
{
"type": "button",
"placeholder": "Register!"
}
]
}]
}
Let's say I wanted to parse the JSON above into the following models (I know it doesn't compile due to the Row protocol not corresponding to Decodable):
enum RowType: String, Codable {
case textField
case image
case button
}
protocol Row: Codable {
var type: RowType { get }
}
struct TextFieldRow: Row {
let type: RowType
let placeholder: String
let value: String
enum CodingKey: String {
case type
case placeholder
case value
}
}
struct ImageRow: Row {
let type: RowType
let imageURL: URL
enum CodingKey: String {
case type
case imageURL
}
}
struct ButtonRow: Row {
let type: RowType
let title: String
enum CodingKey: String {
case type
case title
}
}
struct Section: Codable {
let rows: [Row]
let title: String
enum CodingKey: String {
case rows
case title
}
}
struct Response: Codable {
let sections: [Section]
enum CodingKey: String {
case sections
}
}
// Parsing the response using the Foundation JSONDecoder
let data: Data // From network
let decoder = JSONDecoder()
do {
let response = try decoder.decode(Response.self, from: data)
} catch {
print("error: \(error)")
}
Is there a way to make the Swift code above Codable compliant?
I know you can manually solve this by first grabbing each Row's type string and then creating the right type of Row model as well as changing them from structs to classes and letting the Row protocol be a superclass instead. But is there a way that requires less manual labour?
Using an enum with associated value is the best option:
Consider this enum:
enum Row: Decodable {
case textField(TextFieldRow)
case image(ImageRow)
// and other cases
case unknown
enum CodingKeys: String, CodingKey {
case type
}
public init(from decoder: Decoder) throws {
do {
let selfContainer = try decoder.singleValueContainer()
let typeContainer = try decoder.container(keyedBy: CodingKeys.self)
let type = try typeContainer.decode(String.self, forKey: .type)
switch type {
case "textField": self = .textField( try selfContainer.decode(TextFieldRow.self) )
case "Image": self = .image( try selfContainer.decode(ImageRow.self) )
// and other cases
default: self = .unknown
}
}
}
}
With these changes:
struct TextFieldRow: Decodable {
let placeholder: String?
let value: String?
}
struct ImageRow: Decodable {
let imageURL: URL
}
// and so on
Now this will decode like a charm:
// Minmal testing JSON
let json = """
[
{
"type": "image",
"imageURL": "https://example.com/image.jpg"
},
{
"type": "textField",
"value": "",
"placeholder": "Username"
},
{
"type": "textField",
"placeholder": "password"
}
]
""".data(using: .utf8)!
let decoder = JSONDecoder()
print( try! decoder.decode([Row].self, from: json) )
You can now add any other case you need to the decoder to build your application builder app.