I have been struggling with this for over a day now and Im not sure why I keep getting the Missing Key error when I clearly defined the key in my struct. Your help would be really appreciated. Please help clear up my confusion on how to decode nested types in JSON.
I am trying to parse the following JSON response but keep getting the Missing Key error message when I run my app. Below is the message I get back from the service.
Here is the JSON resonse
{
UserInfo = {
ApplicationArea = {
CreationDateTime = "2018-02-11 21:34:40.646000";
MfgCode = PSUN;
Sender = DM;
};
ServiceStatus = {
StatusCode = 0;
StatusDescription = Success;
System = "Product Info";
};
UserInfoDataArea = {
Email = "john#example.com";
Locale = "en_US";
UserId = 3;
UserName = jdoe;
};
};
}
Here is the ERROR
Missing Key: applicationArea
Debug description: No value associated with key applicationArea ("ApplicationArea").
Below is my struct code for getting the request and to decode the response.
struct UserInfo:Codable {
struct ApplicationArea:Codable {
let creationDateTime: String
let mfgCode: String
let sender: String
private enum CodingKeys: String, CodingKey {
case creationDateTime = "CreationDateTime"
case mfgCode = "MfgCode"
case sender = "Sender"
}
}
let applicationArea: ApplicationArea
enum CodingKeys: String, CodingKey {
case applicationArea = "ApplicationArea"
}
}
Code for creating the request
let apiMethod = HTTPMethod.post
Alamofire.request(
loginURL!,
method: apiMethod,
parameters: params,
encoding: URLEncoding.default,
headers: header)
.responseJSON { (response) -> Void in
switch response.result {
case .success:
print(response.result.value)
let result = response.data
do {
let decoder = JSONDecoder()
let userDetails = try decoder.decode(UserInfo.self, from: result!)
print("Response \(userDetails)")
} catch DecodingError.keyNotFound(let key, let context) {
print("Missing Key: \(key)")
print("Debug description: \(context.debugDescription)")
} catch {
print("Error \(error.localizedDescription)")
}
case .failure(let error):
print("Error \(error)")
}
}
You are making a very common mistake: You're ignoring the root object, the outermost dictionary containing the UserInfo key.
Create a Root struct
struct Root: Decodable {
let userInfo : UserInfo
enum CodingKeys: String, CodingKey { case userInfo = "UserInfo" }
}
And decode
let root = try decoder.decode(Root.self, from: result!)
let userDetails = root.userInfo
Related
When you i try to call .decode() to decode a struct, it returns nil.Can anyone help me?
My app is returning null value from the JSON data, from this line of code:
let newPosData = try JSONDecoder().decode(NewPosDataBase.self, from: responseData)
Here is complete code:
func getNewLFNREXT(invnr: String, completionHandler: #escaping (String, Bool, NewPosDataBase?) -> Void) {
let headers: HTTPHeaders = [
.authorization(username: UserCredentials.shared.username, password: UserCredentials.shared.password),
.accept("application/json")
]
let url:String = Config.middleware + Config.url + "/mobile/lfnrext?invnr=\(invnr)"
AF.request(url, method: .get, parameters: [:], headers: headers).responseJSON { response in
if response.response?.statusCode == 200 {
do {
guard let responseData = response.data else {
completionHandler(errorMessage, false, nil )
return
}
let newPosData = try JSONDecoder().decode(NewPosDataBase.self, from: responseData)
print( newPosData.newPosData?.newLfnrext)
completionHandler("", true, newPosData)
} catch {
completionHandler(errorMessage, false , nil )
}
}else {
let message = self.getErrorMessageFrom(data: response.data, defaultErrorMessage: errorKeineDaten)
completionHandler(message, false, nil)
}
}
}
You need to add the enum CodingKeys if the keys' name differ from the property names of the Codable type.
So, your models should be like,
class NewPosDataBase: Codable {
enum CodingKeys: String, CodingKey {
case newPosData = "NEW_POS_DATA"
}
//rest of the code...
}
class NewPosData: Codable {
enum CodingKeys: String, CodingKey {
case newLfnrext = "NEW_LFNREXT"
case aktuellerZahler = "AKTUELLER_ZAHLER"
case gpsStd = "GPS_STD"
}
//rest of the code...
}
Note: There is no need to give any init implementation in the models unless you need them for any specific purpose.
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?
The JSON from server looks like this:
A dictionary where the value is another dictionary.
{
"S1": {
"vpn_status": 2,
"vpn_name": "vpn1"
},
"S2": {
"vpn_status": 1,
"vpn_name": "vpn2"
}
}
I have created the following struct to parse it.
public struct ServerStatusResult {
public let vpnName: String
public let status: Int
init?(json: [String: Any]) {
guard
let vpnName = json["vpn_name"] as? String,
let status = json["vpn_status"] as? Int
else {
return nil
}
self.vpnName = vpnName
self.status = status
}
}
And the function to call the server is:
typealias serverStatusCompletedClosure = (_ status: Bool, _ result: Dictionary<String,ServerStatusResult>?, _ error: ServiceError?)->Void
func serverStatus(email: String, password: String, complete: #escaping serverStatusCompletedClosure) {
let url = URL(string: "...")!
try? self.httpClient.get(url: url,
token: "...",
email: email,
password: password)
{ (data, response, error) in
if let error = error {
complete(false, nil, ServiceError.invalidSession)
} else if let httpResponse = response as? HTTPURLResponse {
switch (httpResponse.statusCode) {
case 200:
var result: [String:ServerStatusResult]? = nil
result = try! JSONSerialization.jsonObject(with: data!, options: []) as! Dictionary<String, ServerStatusResult>
complete(true, result, nil)
This is where my json transformation fails.
Could not cast value of type '__NSDictionaryI' (0x7fff8eaee9b0) to
'app.ServerStatusResult' (0x10021dec0).
What am I missing please?
You can solve it by using Decodable and using a dictionary
First make your struct conform to Decodable
public struct ServerStatusResult: Decodable {
public let vpnName: String
public let status: Int
enum CodingKeys: String, CodingKey {
case vpnName = "vpn_name"
case status = "vpn_status"
}
}
and then the decoding is easy
do {
let result = try JSONDecoder().decode([String: ServerStatusResult].self, from: data)
print(result) //or in you case complete(true, result, nil)
} catch {
print(error)
}
You get an array of dictionary [[String: Any]]
Create a struct for a dictionary and if a dictionary has another dictionary inside it then create another struct for the inner Dictionary and create an object in the outer struct for innner dictionary json.
You can use codeable to parse your json easily, by inheriting your struct with codeable
I'm trying to access the first result from this query:
https://www.instagram.com/web/search/topsearch/?query=_myUsername
I'm able to get a JSON object like so:
var request = URLRequest(url: URL(string: api)!)
request.httpMethod = "GET"
let task = URLSession.shared.dataTask(with: request) { data, response, error in
guard let data = data, error == nil else { // check for fundamental networking error
print("error=\(error ?? "" as! Error)")
return
}
do {
let jsonResponse = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any]
completionHandler(jsonResponse,nil)
} catch let parsingError {
print("Error", parsingError)
}
if let httpStatus = response as? HTTPURLResponse, httpStatus.statusCode != 200 { // check for http errors
print("statusCode should be 200, but is \(httpStatus.statusCode)")
print("response = \(String(describing: response))")
}
}
task.resume()
The result is a JSON object that omits the first user in "users". For example, if I parse the JSON object to get the username of the first user in the result like this...
if let users = jsonResponse!["users"] as? [Any] {
if let first = users.first as? [String: Any] {
if let user = first["user"] as? [String: Any] {
self.igUser = user["username"] as! String
... It returns the username of the 'position = 1' user, while I actually want the 'position = 0' user. Am I parsing this wrong?
As you can see there is a key position you should assume that the list isn't sorted. You have to find the nth element of the list.
The minimal Codable implementation would be:
struct TopSearchAPIResponse: Codable {
let users: [User]
//let places, hashtags: [Type] // As these two are empty arrays you don't know
// their type in advance. So you can omit them
// for now. When you know their type you can
// use them by providing actual type.
let hasMore: Bool
let rankToken: String
let clearClientCache: Bool
let status: String
struct User: Codable {
let position: Int
let user: UserInfo
struct UserInfo: Codable {
let pk: String
let username: String
let fullName: String
let isPrivate: Bool
let profilePicURL: URL
let profilePicID: String?
let isVerified: Bool
let hasAnonymousProfilePicture: Bool
let followerCount: Int
let reelAutoArchive: ReelAutoArchive
let byline: String
let mutualFollowersCount: Int
let unseenCount: Int
private enum CodingKeys: String, CodingKey {
/* This enum is necessary as we want profile_pic_url & profile_pic_id
to be decoded as profilePicURL & profilePicID respectively (instead of
profilePicUrl & profilePicId) so that we follow Swift conventions */
case pk
case username
case fullName
case isPrivate
case profilePicURL = "profilePicUrl"
case profilePicID = "profilePicId"
case isVerified
case hasAnonymousProfilePicture
case followerCount
case reelAutoArchive
case byline
case mutualFollowersCount
case unseenCount
}
enum ReelAutoArchive: String, Codable {
case off
case on
case unset
}
}
}
}
You will use it as:
do {
let jsonDecoder = JSONDecoder()
jsonDecoder.keyDecodingStrategy = .convertFromSnakeCase
let response = try jsonDecoder.decode(TopSearchAPIResponse.self, from: data)
if let firstUser = response.users.first(where: { $0.position == 0 }) {
print(firstUser.user.username) // prints "myusernameisverygay"
}
} catch {
print(error)
}
Note: Some modifications had been made after the answer was accepted.
Can anyone see what I'm missing? I can't decode anything past results. Nothing will print under results. I've reviewed several other posts that are relevant to JSON/Swift but still don't understand what I'm doing wrong. This is my JSON:
{
"results": [
{
"user.ldap.principal": "OHWIL3336IPM101",
"common.os_version": "11.4",
"common.wifi_mac_address": "100caef1001d",
"common.status": "ACTIVE",
"common.creation_date": "2018-17-05T16:42:49.000Z",
"ios.iPhone UDID": "a8a7a2e52359353dfbacf026a4fada9ew1cb4c10",
"user.ldap.user_attributes.custom1": [
"3336"
],
"common.SerialNumber": "F9FWEF74GHMN",
"common.uuid": "01cd1ed3-b3af-48c0-8499-654c0a9ab996"
}
],
"totalCount": 1,
"resultCount": 1,
"searchTimeMillis": 1,
"currentServerTimeMilliseconds": 1531558334959,
"hasMore": false
}
Here is what I have currently.
struct DeviceData: Codable {
let results: [Result]
let totalCount, resultCount, searchTimeMillis, currentServerTimeMilliseconds: Int
let hasMore: Bool
}
struct Result: Codable {
let commonOSVersion, commonStatus, commonImei, commonCreationDate: String?
let iosDeviceName, commonUUID, userLDAPPrincipal, commonWifiMACAddress: String?
let iosIPhoneUDID: String?
let userLDAPUserAttributesCustom1: [String]?
let commonSerialNumber: String?
let userLDAPGroupsName: [String]?
let iosIPhoneICCID: String?
enum CodingKeys: String, CodingKey {
case commonOSVersion = "common.os_version"
case commonStatus = "common.status"
case commonImei = "common.imei"
case commonCreationDate = "common.creation_date"
case iosDeviceName = "ios.DeviceName"
case commonUUID = "common.uuid"
case userLDAPPrincipal = "user.ldap.principal"
case commonWifiMACAddress = "common.wifi_mac_address"
case iosIPhoneUDID = "ios.iPhone UDID"
case userLDAPUserAttributesCustom1 = "user.ldap.user_attributes.custom1"
case commonSerialNumber = "common.SerialNumber"
case userLDAPGroupsName = "user.ldap.groups.name"
case iosIPhoneICCID = "ios.iPhone ICCID"
}
}
Trying to decode:
let decoder = JSONDecoder()
guard let data = data else {return}
do {
let json = try decoder.decode(DeviceData.self, from: data)
dump(json)
print(json.commonImei) //Does not print - Does not auto-populate - Error Here
}
catch let jsonError {
print("JSON Failed to Decode: ", jsonError)
}
Error:
Value of type 'DeviceData' has no member 'commonImei'
The json will print to the console in full but if I try to print any fields within Result (results) the values don't auto populate and I receive an error. Am I missing something with decoding?
You need do-catch
do {
let decoder = JSONDecoder()
let json = try decoder.decode(DeviceData.self, from: data)
dump(json)
print(json.results[0].commonImei)
}
catch {
print(error)
}
//
The json represents an object of the struct DeviceData which doesn't contain commonImei directly , but it has an array results where all it's elemnts contain that key