Swift 4 JSON Decode Root Array - ios

I am having problems in decoding JSON response using Swift 4 Decoding Functionality.
I have main construct and it has one inner construct var hr_employees: [Employee]? = []. The problem is JSON not mapping for 'var hr_employees: [Employee]? = [].
I am getting correct values forthe three root values response_status,access_level,session_token.
////////////Code for Struct////////////////////////
struct EmployeeData: Codable {
var response_status:Int=0
var access_level:Int=0
var session_token:String=""
var hr_employees: [Employee]? = []
}
private enum CodingKeys: String, CodingKey {
case response_status="response_status"
case access_level="access_level"
case session_token="session_token"
case hr_employees="hr_employees"
}
init() {
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
response_status = try values.decode(Int.self, forKey: .response_status)
do{
session_token = try values.decode(String.self, forKey: .session_token)
}catch {
print( "No value associated with key title (\"session_token\").")
}
do{
access_level = try values.decode(Int.self, forKey: .access_level)
}
catch {
print( "No value associated with key access_level ")
}
}
/////////////////Inner Struct///////////////////////
struct Employee: Codable {
var userId:Int=0
var nameFirst:String=""
var nameLast:String=""
var position:String=""
var company:String=""
var supervisor:String=""
var assistant:String=""
var phone:String=""
var email:String=""
var address:String=""
var gender:String=""
var age:Int=0
var nationality:String=""
var firstLanguage:String=""
var inFieldOfView:String = "0"
var photo:String="user-default"
var status:String="3"
}
////////////Following is the JSON//////////////////////
{
"response_status":1
,"access_level":2
,"hr_employees":[
{
"user_id":4226
,"name_last":"Sampe"
,"name_first":"Frederica"
,"position":"Systems Maint"
,"phone":"123456"
,"email":"omega#demo.mobile"
,"address":"00100 Helsinki 1"
,"age":67
,"company":"Omega Enterprise"
}
,{
"user_id":5656
,"name_last":"Aalto"
,"name_first":"Antero"
,"position":"Programming Methodology and Languages Researcher"
,"supervisor":"Mayo Georgia"
,"phone":"123456"
,"email":"omega#demo.mobile"
,"address":"00100 Finland "
,"age":51
,"company":"Omega Fire Related Equipment"
}
]
}

One problem is that what is in the JSON does not match your definition of Employee. For example nameFirst is not present and name_first is.
Another is that you have a custom implementation of init(from:), and it never fetches the hr_employees value!

Quite a few things for you to improve on:
Your Structs can be improved to harness automation capability of the Codable protocol.
You need to understand why you're using a CodingKeys enum
and in your case... also where best to have it (hint: inside the Struct itself)
You need to know which parameters need to be optional and why
this depends on your json structure ofcourse
If the parameters are to have a default value then there's a whole different process you need to follow; like having your own init(from:Decoder)
which you have to a certain extent but doesn't really handle everything in it's current state
Based on your given JSON example, you can simply do the following.
However... do note that this is not designed to provide default values. i.e. If a key is missing in the json, like status for example, then the parameter status in your Employee struct will be nil rather than a default value of "3".
struct EmployeeData: Codable {
var responseStatus: Int
var accessLevel: Int
/*
sessionToken is optional because as per your JSON
it seems it not always available
*/
var sessionToken: String?
var hrEmployees: [Employee]
/*
CodingKeys is inside the struct
It's used if the JSON key names are different than
the ones you plan to use.
i.e. JSON has keys in snake_case but we want camelCase
*/
enum CodingKeys: String, CodingKey {
case responseStatus = "response_status"
case accessLevel = "access_level"
case sessionToken = "session_token"
case hrEmployees = "hr_employees"
}
}
struct Employee: Codable {
var userId: Int
var nameFirst: String
var nameLast: String
var position: String
var company: String
var supervisor: String?
var assistant: String?
var phone: String
var email: String
var address: String
var gender: String?
var age: Int
var nationality: String?
var firstLanguage: String?
var inFieldOfView: String?
var photo: String?
var status: String?
enum CodingKeys: String, CodingKey {
case userId = "user_id"
case nameFirst = "name_first"
case nameLast = "name_last"
case firstLanguage = "first_language"
case inFieldOfView = "in_field_of_view"
/*
Keys names that are same in json as well as in your
model need not have a raw string value
but must be defined if it's to be encoded/decoded
from the json else it can be omitted and a default
value will be required which won't affect the encoding
or decoding process
*/
case position
case company
case supervisor
case assistant
case phone
case email
case address
case gender
case age
case nationality
case photo
case status
}
}
Check:
do {
let employeeData = try JSONDecoder().decode(EmployeeData.self,
from: jsonAsData)
print(employeeData)
}
catch {
/*
If it comes here then debug, it's most probably nil keys
meaning you need more optional parameters in your struct
*/
print(error)
}
If you want default values in your Struct and the above example is a dealbreaker for you then check the following answer:
https://stackoverflow.com/a/44575580/2857130

Related

Convert null values to default strings in parsing JSON using JSONDecoder in Swift

I am trying to parse some JSON in Swift using JSONDecoder where the JSON occasionally has null values. I would like to put in a default instead.
The following allows me to handle it but the nulls cause problems later.
struct Book: Codable {
let title : String
let author: String?
}
Is there a way to do something like (following does not compile), perhaps using an initializer?:
struct Book: Codable {
let title : String
let author: String ?? "unknown"
}
Thanks for any suggestions
This could be address by manually decoding as described here.
The other way to go would be to have the stored properties reflect the data exactly, and then have a computed var for the case of providing a non-optional value.
struct Book: Codable {
let title : String
let author: String?
var displayAuthor: String {
return author ?? "unknown"
}
}
The other reason this might be appealing is it preserves the optional value should you need to check if the value exists at all in the future.
You can achieve this using the custom init(decoder:) method definition. Use the decodeIfPresent API and give the property your desired default value if the try fails. Or you can use the computed property method mentioned by #dktaylor. Here the code you need:
struct Book {
let title : String
let author: String
enum CodingKeys: String, CodingKey {
case title, author
}
}
extension Book: Codable {
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
title = try container.decode(String.self, forKey: .title)
author = try container.decodeIfPresent(String.self, forKey: .author) ?? "unknown"
}
}
You can also achieve this with a property wrapper:
#propertyWrapper
struct OnNil<T> {
let value: T
init(_ value: T) {
self.value = value
}
private var _wrappedValue: T?
var wrappedValue: T! {
get { _wrappedValue ?? value }
set { _wrappedValue = newValue }
}
}
struct SomeStruct {
let title : String
#OnNil("unknown")
let author: String!
}
The benefits of using a property wrapper like this is that you don't have to soil your object with utility methods, and you don't have to fiddle with the decode function. The downside obviously is that the syntax looks kind of odd, you have to make some variables implicitly unwrapped optional, and it makes your code slightly harder to debug due to the nature of property wrappers.

Optional list of NSData in Realm - Swift

I need to save a list of images as NSData in Realm. I tried using Realm optional but realmOptional<NSdata> can't be used because realmOptional does not conform to type NSDate.
Is there a way to do it?
Edit: Basically all I want is to be able to store a list of NSData but optional
something like:
#objc dynamic var photos: List<NSData>?
Solution for optional List types when using Decodable
In my case I needed an optional List because I'm decoding json into Realm objects, and it's possible that the property might not exist in the json data. The typical workaround is to manually decode and use decodeIfPresent(). This isn't ideal because it requires boilerplate code in the model:
class Driver: Object, Decodable {
var vehicles = List<Vehicle>()
var name: String = ""
// etc
public required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.vehicles = try container.decodeIfPresent(List<Vehicle>.self, forKey: .vehicles) ?? List<Vehicle>()
self.name = try container.decode(String.self, forKey: .name)
// etc
}
}
However, we can avoid the boilerplate by extending KeyedDecodingContainer with an overload for List types. This will force all lists to be decoded using decodeIfPresent():
Realm v5:
class Driver: Object, Decodable {
var vehicles = List<Vehicle>()
var name: String = ""
//etc
}
class Vehicle: Object, Decodable {
var drivers = List<Driver>()
var make: String = ""
// etc
}
extension KeyedDecodingContainer {
// This will be called when any List<> is decoded
func decode<T: Decodable>(_ type: List<T>.Type, forKey key: Key) throws -> List<T> {
// Use decode if present, falling back to an empty list
try decodeIfPresent(type, forKey: key) ?? List<T>()
}
}
Realm v10+:
class Driver: Object, Decodable {
#Persisted var vehicles: List<Vehicle>
#Persisted var name: String = ""
// etc
}
class Vehicle: Object, Decodable {
#Persisted var drivers: List<Driver>
#Persisted var make: String = ""
// etc
}
extension KeyedDecodingContainer {
// This will be called when any #Persisted List<> is decoded
func decode<T: Decodable>(_ type: Persisted<List<T>>.Type, forKey key: Key) throws -> Persisted<List<T>> {
// Use decode if present, falling back to an empty list
try decodeIfPresent(type, forKey: key) ?? Persisted<List<T>>(wrappedValue: List<T>())
}
}
Edit: Clarified code examples, fixed typo
The List cannot be optional, but the Objects in the list can be optional, you have to declare it like:
#Persisted var value: List<Type?>
Here is the link with the Supported data types
https://www.mongodb.com/docs/realm/sdk/swift/data-types/supported-property-types/
according to https://realm.io/docs/swift/latest/#property-cheatsheet you can not define optional lists in realm

How to execute codable values within viewDidload and Search option by name field using swift 4.2?

I am using codable and trying to get data from JSON response. Here, I can’t able to print particular value into viewDidload using swift 4.2 and I am using search but I want to assign filteredData with name values.
my code
struct Root: Codable {
let status: Int
let message: String
let country: [Country]
let cost: Double
let data: [Datum]
}
struct Country: Codable {
let id: Int
let master: String
let type, active: Int
}
struct Datum: Codable {
let id, userID, country, card: Int
let category: Int
let title, description: String
let cost: Double
let attachment, createDate, deviceID, appType: String
let location: String
let user: User
let appTransCard: AppTransCard
let appTransMaster: AppTransMaster
enum CodingKeys: String, CodingKey {
case id
case userID = "user_id"
case country, card, category, title, description, cost, attachment
case createDate = "create_date"
case deviceID = "device_id"
case appType = "app_type"
case location, user
case appTransCard = "app_trans_card"
case appTransMaster = "app_trans_master"
}
}
struct AppTransCard: Codable {
let card: String
}
struct AppTransMaster: Codable {
let master: String
}
struct User: Codable {
let firstname, lastname: String
}
I need to get values within viewdidload from Root.cost.
override func viewDidLoad() {
super.viewDidLoad()
// here I need to print Root.cost value
}
You need to create a new variable of type Root
for example var root : Root
and on viewDidLoad you make the request (decode)
depend if it's from API or local JSON file.
and then the result from the decode will save inside root variable
root = result
then you can access root.cost
I also recommend you to see this video
it will help you understand how to use decodable

Swift extension - Constrained extension must be declared on the unspecialized generic type 'Array'

I have an API that returns a JSON array of objects. I've setup the structure to look as follows:
typealias MyModels = [MyModel]
struct MyModel: Codable {
let field1: String
let field2: String
let mySubModel: SubModel?
enum CodingKeys: String, CodingKey {
case field1 = "Field1"
case field2 = "Field2"
case mySubModel = "MySubModel"
}
}
struct SubModel: Codable {
let subModelField1: String
let subModelField2: String
enum CodingKeys: String, CodingKey {
case subModelField1 = "SubModelField1"
case subModelField2 = "SubModelField2"
}
}
What I want to do is add this extension, supplying a path var (the NetworkModel protocol provides some base functionality for API operations):
extension MyModels: NetworkModel {
static var path = "the/endpoint/path"
}
I don't have any issues in other model/struct classes that I setup in this way when the base is an object or json key. However, since this one is different and simply is an array, I get this error when I put that extension in the class:
Constrained extension must be declared on the unspecialized generic type 'Array' with constraints specified by a 'where' clause
I've done some digging and tried a few things with a where clause on the extension, but I'm just a bit confused as to what it wants. I'm sure it's something simple, but any thoughts on this? If I need to go about it a different way with the typealias above, I'm fine with that. Thanks in advance!
The error is basically telling you to do this:
extension Array : NetworkModel where Element == MyModel {
static var path = "the/endpoint/path"
}
You can't simply make an extension of [MyModel].
Although Sweeper answered the question appropriately based on the initial question, I wanted to provide an alternative approach, even if potentially slightly more complex. This can be accomplished by overriding the Decodable side of things and manually putting the models into a list:
struct MyModels: Codable {
var modelList: [MyModel] = []
public init(from decoder: Decoder) throws {
var container = try decoder.unkeyedContainer()
let metaType = (Account.self as Decodable.Type)
while !container.isAtEnd {
let subdecoder = try container.superDecoder()
if let model = try metaType.init(from: subdecoder) as? MyModel {
modelList.append(model)
}
}
}
}
struct MyModel: Codable {
let subModelField1: String
let subModelField2: String
enum CodingKeys: String, CodingKey {
case subModelField1 = "SubModelField1"
case subModelField2 = "SubModelField2"
}
}
extension MyModels: NetworkModel {
static var path = "the/endpoint/path"
}

Pass A Generic Codable Type as Parameter to a Method for Saving to Realm

I am trying to create a generic method for decoding some JSON data. I need to cast the JSON data into Objects for later saving in Realm.
For example: Getting the makes, models, colors, and body types of vehicles.
In each of my JSON calls to the results are formatted exactly the same (See my structs at the end of the question). For brevity, I am only showing you Makes and Models, but Colors and Body Types are exactly the same, just with the name changes.
I currently have four methods that I call where Makes.self is replaced with one of the other structs.
if let results = try? JSONDecoder().decode(Makes.self, from: jsonData)) {
DispatchQueue.main.async {
//save data to realm
self?.save(objects: results.data )
}
}
Once I get my JSON Data, I would like to send my data to a generic method for processing.
So I could call something like :
process(jsonData, modelType: Makes.self)
process(jsonData, modelType: Models.self)
process(jsonData, modelType: Colors.self)
process(jsonData, modelType: Bodies.self)
I have tried variations on generic types but I can't seem to get it right.
func process<T:Codable>(_ jsonData: Data, modelType: T.Type = T.self) {
if let results = try? JSONDecoder().decode(modelType.self, from: jsonData) {
DispatchQueue.main.async {
//save data to realm
self?.save(objects:results.data)
}
}
}
How can I pass a Decodable Protocol type as a generic?
MAKES
import RealmSwift
struct Makes: Codable {
let result: String
let data: [Make]
enum CodingKeys: String, CodingKey {
case result = "Result"
case data = "Data"
}
}
class Make: Object, Codable {
#objc dynamic var key: String
#objc dynamic var value: String
#objc dynamic var shortCode: String
#objc dynamic var active: Bool
enum CodingKeys: String, CodingKey {
case key = "Key"
case value = "Value"
case shortCode = "ShortCode"
case active = "Active"
}
}
MODELS
import RealmSwift
struct Makes: Codable {
let result: String
let data: [Make]
enum CodingKeys: String, CodingKey {
case result = "Result"
case data = "Data"
}
}
class Make: Object, Codable {
#objc dynamic var key: String
#objc dynamic var value: String
#objc dynamic var shortCode: String
#objc dynamic var active: Bool
enum CodingKeys: String, CodingKey {
case key = "Key"
case value = "Value"
case shortCode = "ShortCode"
case active = "Active"
}
}
Why would you expect any Codable type to have a data property? results.data cannot work... You need to make T a subclass of Object to be able to save it to Realm and also Decodable to be able to pass it to the decode method.
func process<T:Object>(_ jsonData: Data, modelType: T.Type) where T:Decodable {
if let results = try? JSONDecoder().decode(modelType.self, from: jsonData) {
DispatchQueue.main.async {
//save data to realm
self?.save(objects:results)
}
}
}

Resources