I try to deserialize some JSON on Swift 4.2 with Codable Protocol.
My Json:
{
"status":1,
"data":[
[
{
"id":"4klJeiCKTs",
"body":"first",
"da":"1442236233",
"dm":"1442236233"
},
{
...
}
]
]
}
my structures and code:
struct GetEntriesRequest: Decodable{
var status: Int
var data: [NestedArrayGetEntries]
}
struct NestedArrayGetEntries: Decodable{
var elements: [GetEntriesDataFromSession]
}
struct GetEntriesDataFromSession: Decodable{
var id: String
var body: String
var da: String
var dm: String
}
...
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
do {
let root = try decoder.decode(GetEntriesRequest.self, from: data)
dataSession = root
} catch { print(error) }
Also, I tried this struct
var data: [[GetEntriesDataFromSession]]
but without any success.
data key is a nested array and there is no elements key
var data: [NestedArrayGetEntries]
with
var data: [[NestedArrayGetEntries]]
struct GetEntriesRequest: Codable {
let status: Int
let data: [[NestedArrayGetEntries]]
}
struct NestedArrayGetEntries: Codable {
let id, body, da, dm: String
}
You don't also need a snakeCase here
do {
let root = try JSONDecoder().decode(GetEntriesRequest.self, from: data)
dataSession = root
} catch {
print(error)
}
Related
I know this type of question seems to be answered a lot but I really can't seem to make this work. I'm trying to decode some JSON data into my data structs. I think the problem is there. I may have my data model wrong, but can't quite work it out. The data is not an array, there is an array within it. Its trying to decode a dictionary into array but when I try to initialise my results variable as something other than array it won't build. I'll submit my code and the JSON data in the hopes someone can shed light!
The error I'm getting is:
JSON decode failed: Swift.DecodingError.typeMismatch(Swift.Array, Swift.DecodingError.Context(codingPath: [], debugDescription: "Expected to decode Array but found a dictionary instead.", underlyingError: nil))
thank you so much
import SwiftUI
struct DataFormatted: Codable, Hashable {
var deliveryPoints: [DeliveryPoints]
var deliveryPointCount: Int
var postalCounty: String
var traditionalCounty: String
var town: String
var postCode: String
}
struct DeliveryPoints: Codable, Hashable {
var organisationName: String
var departmentName: String
var line1: String
var line2: String
var udprn: String
var dps: String
}
struct ContentView: View {
// I reckon the error is here:
#State private var results = [DataFormatted]()
var body: some View {
VStack{
List{
ForEach(results, id: \.self) { result in
Text(result.postalCounty)
}
}
}
.task {
await loadData()
}
}
func loadData() async {
guard let url = URL(string: "https://pcls1.craftyclicks.co.uk/json/rapidaddress?key=APIKEY&postcode=aa11aa&response=data_formatted") else {
print("Invalid URL")
return
}
var request = URLRequest(url: url)
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.httpMethod = "GET"
do {
let (data, _) = try await URLSession.shared.data(for: request)
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let decodedResponse = try decoder.decode([DataFormatted].self, from: data)
results = decodedResponse
} catch let jsonError as NSError {
print("JSON decode failed: \(jsonError)")
}
}
}
JSON Data:
{
"delivery_points":[
{
"organisation_name":"THE BAKERY",
"department_name":"",
"line_1":"1 HIGH STREET",
"line_2":"CRAFTY VALLEY",
"udprn":"12345678",
"dps":"1A"
},
{
"organisation_name":"FILMS R US",
"department_name":"",
"line_1":"3 HIGH STREET",
"line_2":"CRAFTY VALLEY",
"udprn":"12345679",
"dps":"1B"
}
],
"delivery_point_count":2,
"postal_county":"POSTAL COUNTY",
"traditional_county":"TRADITIONAL COUNTY",
"town":"BIG CITY",
"postcode":"AA1 1AA"
}
try something like this:
struct DataFormatted: Codable {
var deliveryPoints: [DeliveryPoint]
var deliveryPointCount: Int
var postalCounty: String
var traditionalCounty: String
var town: String
var postcode: String // <-- postcode
}
struct DeliveryPoint: Codable {
var organisationName: String
var departmentName: String
var line1: String?
var line2: String?
var line3: String?
var udprn: String
var dps: String
}
and use it like this:
let apiResponse = try decoder.decode(DataFormatted.self, from: data)
EDIT-1: here is the code I used for testing:
// -- here, default values for convenience
struct DataFormatted: Codable {
var deliveryPoints: [DeliveryPoint] = []
var deliveryPointCount: Int = 0
var postalCounty: String = ""
var traditionalCounty: String = ""
var town: String = ""
var postcode: String = "" // <-- postcode
}
struct DeliveryPoint: Hashable, Codable { // <-- here
var organisationName: String
var departmentName: String
var line1: String?
var line2: String?
var line3: String?
var udprn: String
var dps: String
}
struct ContentView: View {
#State private var results = DataFormatted()
var body: some View {
VStack{
Text(results.postalCounty)
Text(results.town)
List {
ForEach(results.deliveryPoints, id: \.self) { point in
Text(point.organisationName)
}
}
}
.task {
await loadData()
}
}
func loadData() async {
let apikey = "your-key" // <-- here
guard let url = URL(string: "https://pcls1.craftyclicks.co.uk/json/rapidaddress?key=\(apikey)&postcode=aa11aa&response=data_formatted") else {
print("Invalid URL")
return
}
do {
let (data, _) = try await URLSession.shared.data(from: url)
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
results = try decoder.decode(DataFormatted.self, from: data) // <-- here
} catch {
print("JSON decode failed: \(error)")
}
}
}
I am working on an app that fetches the data from JSON and displays it.
However, I am stuck with an error saying Instance method 'appendInterpolation(_:formatter:)' requires that '[String : Int]' inherit from 'NSObject'
Here is my data structure:
struct Data: Codable {
var message: String
var data: Objects
}
struct Objects: Codable {
var date: String
var day: Int
var resource: String
var stats, increase: [String: Int]
}
Function to fetch the data:
func getData() {
let urlString = "https://russianwarship.rip/api/v1/statistics/latest"
let url = URL(string: urlString)
URLSession.shared.dataTask(with: url!) { data, _, error in
if let data = data {
do {
let decoder = JSONDecoder()
let decodedData = try decoder.decode(Data.self, from: data)
self.data = decodedData
} catch {
print("Hey there's an error: \(error.localizedDescription)")
}
}
}.resume()
}
And a ContentView with the #State property to pass the placeholder data:
struct ContentView: View {
#State var data = Data(message: "", data: Objects(date: "123", day: 123, resource: "", stats: ["123" : 1], increase: ["123" : 1]))
var body: some View {
VStack {
Button("refresh") { getData() }
Text("\(data.data.date)")
Text("\(data.data.day)")
Text(data.message)
Text("\(data.data.stats)") //error is here
Here is an example of JSON response
I wonder if the problem is in data structure, because both
Text("\(data.data.date)")
Text("\(data.data.day)")
are working just fine. If there are any workarounds with this issue – please, I would highly appreciate your help!:)
stats is [String: Int], and so when you want to use it, you need to supply the key to get the value Int, the result is an optional that you must unwrap or supply a default value in Text
So use this:
Text("\(data.data.stats["123"] ?? 0)")
And as mentioned in the comments, do not use Data for your struct name.
EDIT-1: there are two ways you can make the struct fields camelCase; one is using the CodingKeys as shown in ItemModel, or at the decoding stage, as shown in the getData() function. Note, I've also updated your models to make them easier to use.
struct DataModel: Codable {
var message: String
var data: ObjectModel
}
struct ObjectModel: Codable {
var date: String
var day: Int
var resource: String
var stats: ItemModel
var increase: ItemModel
}
struct ItemModel: Codable {
var personnelUnits: Int
var tanks: Int
var armouredFightingVehicles: Int
// ...
// manual CodingKeys
// enum CodingKeys: String, CodingKey {
// case tanks
// case personnelUnits = "personnel_units"
// case armouredFightingVehicles = "armoured_fighting_vehicles"
// }
}
struct ContentView: View {
#State var dataModel = DataModel(message: "", data: ObjectModel(date: "123", day: 123, resource: "", stats: ItemModel(personnelUnits: 123, tanks: 456, armouredFightingVehicles: 789), increase: ItemModel(personnelUnits: 3, tanks: 4, armouredFightingVehicles: 5)))
var body: some View {
VStack {
Button("get data from Server") { getData() }
Text("\(dataModel.data.date)")
Text("\(dataModel.data.day)")
Text(dataModel.message)
Text("\(dataModel.data.stats.armouredFightingVehicles)") // <-- here
}
}
func getData() {
let urlString = "https://russianwarship.rip/api/v1/statistics/latest"
if let url = URL(string: urlString) {
URLSession.shared.dataTask(with: url) { data, _, error in
if let data = data {
do {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase // <-- here
dataModel = try decoder.decode(DataModel.self, from: data)
} catch {
print("--> error: \(error)")
}
}
}.resume()
}
}
}
I have tried following a variety of tutorials, and I am unable to progress on getting data from this API. I did manage to succeed on a simpler JSON ], but this one is eating up my time.
First, the JSON:
{
"object": {
"array": [
{
"id": 48,
"name": "Job No.# 48",
"description": "blah",
"start_at": "2021-03-05T13:15:00.000+11:00",
"end_at": "2021-03-05T14:15:00.000+11:00",
"map_address": "blah road"
},
{
"id": 56,
"name": "Job No.# 56",
"description": "Do it",
"start_at": "2021-06-22T11:30:00.000+10:00",
"end_at": "2021-06-22T13:30:00.000+10:00",
"map_address": " blah"
}
],
"person": {
"id": 52,
"first_name": "Bob",
"last_name": "Newby",
"mobile": "0401111111",
"email": "bob#mail.com"
}
}
}
And now my attempt at decoding it:
struct api_data: Codable {
let object : Object
}
struct Object: Codable {
let array : [array]
let person : Person
}
struct array: Codable, Identifiable {
let id : Int?
let start_at, end_at : Date?
let duration : Float?
let cancellation_type : String?
let name, description, address, city, postcode, state : String?
}
struct Person: Codable, Identifiable {
let id : Int?
let first_name, last_name, mobile, email : String?
}
class FetchShifts: ObservableObject {
#Published var shifts = [Shifts]()
init() {
let url = URL(string: "realURLhiddenForPrivacy")!
var request = URLRequest(url: url)
request.httpMethod = "GET"
request.setValue("myToken", forHTTPHeaderField: "Authorization")
URLSession.shared.dataTask(with: request) {(data, response, error) in
do {
if let array_data = data {
let array_data = try JSONDecoder().decode([array].self, from: array_data)
DispatchQueue.main.async {
self.array = array_data
}
} else {
print("No data")
}
} catch {
print(error)
}
}.resume()
}
}
And how I attempt to present it:
#ObservedObject var fetch = FetchArray()
var body: some View {
VStack {
List(array.shifts) { shft in
VStack(alignment: .leading) {
Text(shft.name!)
}
}
}
}
}
}
Any help is appreciated, not sure where it is I go wrong here, been at it for 5-7 hours going through tutorials.
I always recommend using app.quicktype.io to generate models from JSON if you're unfamiliar with it. Here's what it yields:
// MARK: - Welcome
struct Welcome: Codable {
let status: String
let payload: Payload
}
// MARK: - Payload
struct Payload: Codable {
let shifts: [Shift]
let worker: Worker
}
// MARK: - Shift
struct Shift: Codable, Identifiable {
let id: Int
let name, shiftDescription, startAt, endAt: String
let mapAddress: String
enum CodingKeys: String, CodingKey {
case id, name
case shiftDescription = "description"
case startAt = "start_at"
case endAt = "end_at"
case mapAddress = "map_address"
}
}
// MARK: - Worker
struct Worker: Codable {
let id: Int
let firstName, lastName, mobile, email: String
enum CodingKeys: String, CodingKey {
case id
case firstName = "first_name"
case lastName = "last_name"
case mobile, email
}
}
Then, to decode, you'd do:
do {
let decoded = try JSONDecoder().decode(Welcome.self, from: shift_data)
let shifts = decoded.payload.shifts
} catch {
print(error)
}
Note that in Swift, it's common practice to use camel case for naming, not snake case, so you'll see that CodingKeys does some conversion for that (there are automated ways of doing this as well).
Update, based on comments:
Your code would be:
if let shiftData = data {
do {
let decoded = try JSONDecoder().decode(Welcome.self, from: shiftData)
DispatchQueue.main.async {
self.shifts = decoded.payload.shifts
}
} catch {
print(error)
}
}
Instead of defining custom keys, you can automatically use
keyDecodingStrategy = .convertFromSnakeCase for your JSON decoder, and could specify custom date format or even throw a custom error in your decoder implementation.
struct Worker: Codable {
let id: Int
let firstName: String?
let lastName: String?
let mobile: String?
let email: String?
}
struct Shift: Codable, Identifiable {
let id: Int
let name: String?
let description: String?
let startAt: Date?
let endAt: Date?
let mapAddress: String?
}
struct Payload: Codable {
let shifts: [Shift]?
let worker: Worker?
}
struct Response: Codable {
let status: String
let payload: Payload?
}
class MyCustomDecoder: JSONDecoder {
override init() {
super.init()
self.keyDecodingStrategy = .convertFromSnakeCase
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZ"
self.dateDecodingStrategy = .formatted(dateFormatter)
}
}
// Usage
if let data = json.data(using: .utf8) {
let response = try MyCustomDecoder().decode(Response.self, from: data)
print(response)
}
I get the following json from an api call but am having trouble making the proper structure in swift and then getting the data as an array.
JSON:
{
"status":"ok",
"users":[
{
"position":0,
"user":{
"pk":"",
"full_name":"",
"username":"",
"profile_pic_url":""
}
},...
]
}
Swift:
class Response: Codable {
var status: String
var users: [User]?
}
class User: Identifiable, Codable {
var uuid = UUID()
var pk: String
var full_name: String
var username: String
var profile_pic_url: String
enum CodingKeys: String, CodingKey {
case
pk = "user.pk",
full_name = "user.full_name",
username = "user.username",
profile_pic_url = "user.profile_pic_url"
}
}
class Fetch: ObservableObject {
#Published var results = [User]()
#Published var resultState = false
#Published var errorState = false
init(url: String) {
self.results = []
let url = URL(string: url)!
URLSession.shared.dataTask(with: url) { data, response, error in
do {
if let data = data {
let results = try JSONDecoder().decode(Response.self, from: data)
DispatchQueue.main.async {
self.results = results.users ?? []
self.resultState = true
}
print("Widget: Ok.")
} else {
self.results = []
self.resultState = true
print("Widget: No data.")
}
} catch {
self.errorState = true
self.resultState = true
print("Widget: Error", error)
}
}.resume()
}
}
Code:
#ObservedObject var fetch = Fetch(url: "")
List(fetch.results) { user in
UserItem(user: user)
}
The problem is that inside array users, it contains an object, this object contains two elements a position attribute and then the user object.
What I think I'm doing wrong is taking the user object.
Can anyone help me out?
Edit:
struct Response: Codable {
let status: String
let users: [UserType]?
}
struct UserType: Codable {
let position: Int
let user: User
}
struct User: Codable {
let pk: String
let full_name: String
let username: String
let profile_pic_url: String
enum CodingKeys: String, CodingKey {
case pk, full_name, username, profile_pic_url
}
}
class Fetch: ObservableObject {
#Published var results = [User]()
#Published var resultState = false
#Published var errorState = false
init(url: String) {
self.results = []
let url = URL(string: url)!
URLSession.shared.dataTask(with: url) { data, response, error in
do {
if let data = data {
let results = try JSONDecoder().decode(Response.self, from: data)
let users = results.users?.map { $0.user }
DispatchQueue.main.async {
self.results = users ?? []
self.resultState = true
}
print("Widget: Ok.")
} else {
self.results = []
self.resultState = true
print("Widget: No data.")
}
} catch {
self.errorState = true
self.resultState = true
print("Widget: Error", error)
}
}.resume()
}
}
List(fetch.results) { user in
UserItem(user: user)
}
You can try this.
struct Response: Codable {
let status: String
let users: [UserWPosition]
var userNoPositions: [UserInfo] { // computed value with only array of userinfo
users.compactMap { $0.user }
}
}
// MARK: - User with position object
struct UserWPosition: Codable {
let position: Int // commenting this will also do no effect
let user: UserInfo
}
// MARK: - UserInfo
struct UserInfo: Codable {
let pk, fullName, username, profilePicURL: String
enum CodingKeys: String, CodingKey {
case pk
case fullName = "full_name"
case username
case profilePicURL = "profile_pic_url"
}
}
Read the comments I added to the code decoding will not code a key that's not added to the struct so commenting out position will have no issue also, the hierarchy of it should be like this now I added a userNoPositions computed value in response to give array of users easily .
Simply to access the array without positions
var resp = try! JSONDecoder().decode(Response.self, from: encoded) // encoded is the data from json
print(resp.userNoPositions) // the array
You need an extra struct that holds the User type
struct UserType: Codable {
let position: Int
let user: User
}
Meaning the top type becomes
struct Response: Codable {
let status: String
let users: [UserType]?
}
You also need to change the CodingKeys enum since it should just contain the property names which mean the enum can be written as
enum CodingKeys: String, CodingKey {
case pk, full_name, username, profile_pic_url
}
For completeness here is the full User type
struct User: Identifiable, Codable {
var uuid = UUID()
var pk: String
var full_name: String
var username: String
var profile_pic_url: String
enum CodingKeys: String, CodingKey {
case pk, full_name, username, profile_pic_url
}
}
and when decoding then you can extract the users array with the map function
do {
let results = try JSONDecoder().decode(Response.self, from: data)
let users = results.users?.map { $0.user }
....
Note that I changed from class to struct because struct is better suited for this but class works as well. I also wonder why the users property is optional, I didn't change that but can the array really be nil?
I got to know struct "Codable" in swift 4.0, *.
So, I tried that when decode josn.
if let jsonData = jsonString.data(using: .utf8) {
let decodingData = try? JSONDecoder().decode(SampleModel.self, from: jsonData)
}
Example sample data model below.
struct SampleModel : Codable {
var no: Int?
var category: Int?
var template_seq: Int?
}
And sample json data is .. below.
{
"data": {
"result" : 1
"total_count": 523,
"list": [
{
"no": 16398,
"category" : 23,
"template_seq" : 1
},
{
"no": -1,
"category" : 23,
"template_seq" : 1
}
]
}
}
But i want filtering wrong data.
If the value of "no" is less than or equal to 0, it is an invalid value.
Before not using codable...below.
(using Alamifre ison response )
guard let dictionaryData = responseJSON as? [String : Any] else { return nil }
guard let resultCode = dictionaryData["result"] as? Bool , resultCode == true else { return nil }
guard let theContainedData = dictionaryData["data"] as? [String:Any] else { return nil }
guard let sampleListData = theContainedData["list"] as? [[String : Any]] else { return nil }
var myListData = [MyEstimateListData]()
for theSample in sampleListData {
guard let existNo = theSample["no"] as? Int, existNo > 0 else {
continue
}
myListData.append( ... )
}
return myListData
how to filter wrong data or invalid data using swift 4.0 Codable ??
you can make codable for inital resonse
Here is your model:
import Foundation
struct Initial: Codable {
let data: DataModel?
}
struct DataModel: Codable {
let result, totalCount: Int
let list: [List]?
enum CodingKeys: String, CodingKey {
case result
case totalCount = "total_count"
case list
}
}
struct List: Codable {
let no, category, templateSeq: Int
enum CodingKeys: String, CodingKey {
case no, category
case templateSeq = "template_seq"
}
}
extension Initial {
init(data: Data) throws {
self = try JSONDecoder().decode(Initial.self, from: data)
}
}
And use it like that :
if let initail = try? Initial.init(data: data) , let list = initail.data?.list {
var myListData = list.filter { $0.no > 0 }
}
Yes, you have to use filters for that with codable:
1: Your struct should be according to your response like that:
struct SampleModel : Codable {
var result: Int?
var total_count: Int?
var list: [List]?
}
struct List : Codable {
var no: Int?
var category: Int?
var template_seq: Int?
}
2: Parse your response using a codable struct like that:
do {
let jsonData = try JSONSerialization.data(withJSONObject: dictionaryData["data"] as Any, options: JSONSerialization.WritingOptions.prettyPrinted)
let resultData = try JSONDecoder().decode(SampleModel.self, from: jsonData)
success(result as AnyObject)
} catch let message {
print("JSON serialization error:" + "\(message)")
}
3: now you can filter invalid data simply:
let filterListData = resultData.list?.filter({$0.no > 0})
let invalidData = resultData.list?.filter({$0.no <= 0})