EDIT: The Code works, the issue was with the imported data set, which did not classify empty fields as number, but as string instead.
My app needs to import values from a JSON file in a similar construct as shown below
[
{
"id": 1,
"string": "Text String",
"int": 6,
"cgfloat": 1.1,
}
]
Import:
id = try container.decode(Int.self, forKey: .id)
string = try container.decode(String.self, forKey: .string)
int = try container.decode(Int.self, forKey: .int)
I have no clue how I can read the value 1.1, neither Double nor Float, CGFloat, Int, nor String works here.
Do I need to change my data structure or is there a way to interpret this value properly in Swift?
this crashes the app:
let cgfloat = try container.decode(CGFloat.self, forKey: . cgfloat)
As requested the full code:
struct Monster: Codable, Comparable {
enum MonsterStatus: String, Identifiable, Codable, Hashable {
var id: Self {
return self
}
case Normal = "Normal"
case Attack = "Attack"
case Hit = "Hit"
case Defend = "Defend"
case Dead = "Dead"
}
enum MonsterType: String, Identifiable, Codable, Hashable {
var id: Self {
return self
}
case Undead = "Undead"
case Human = "Human"
case Dinosaur = "Dinosaur"
case Dwarf = "Dwarf"
case Elf = "Elf"
case Wisp = "Wisp"
case Ghost = "Ghost"
case Beast = "Beast"
case Snake = "Snake"
case Giant = "Giant"
case Demon = "Demon"
case Dragon = "Dragon"
case Error = "Error"
}
struct ImagesCatalog: Equatable, Codable {
var Normal: String
var Attack: String
var Defend: String
var Hit: String
var Dead: String
}
static func < (lhs: Monster, rhs: Monster) -> Bool {
return lhs.id < rhs.id
}
static func == (lhs: Monster, rhs: Monster) -> Bool {
return lhs.id == rhs.id
}
var id: Int
let name: String
let image: String = ""
var imagePrefix:String = ""
var strength: Int = 4
var life: Int = 1
var fleeChance: Int = 0
var isBoss: Bool = false
var flying: Bool = false
var mage: Bool = false
var venomous: Bool = false
var giant: Bool = false
var carnivore: Bool = false
var herbivore: Bool = false
var type: MonsterType
var location: HordeGameData.Location
var isFaceUp: Bool = false
var soundAppear: String = "skeletonWalk4.mp3"
var soundHit: String = "skeletonHit1.mp3"
var soundDefend: String = "skeletonEmerge1.mp3"
var soundAttack: String = "skeletonHit4.mp3"
var soundDead: String = "skeletonShatter1.mp3"
var playSound: Bool = true
let images: ImagesCatalog
var imagePic: String
var scaleFactor: CGFloat = 0.5
var active: Bool = false
var slash: Bool = false
var description: String = ""
var status: MonsterStatus = .Normal
func returnColor() -> Color {
if isFaceUp && isBoss {
return .red
} else {
return .red
}
}
func provideID() -> String {
return String(self.id)
}
mutating func playSoundToggle() {
self.playSound.toggle()
}
mutating func reduceStrengthBy(_ amount: Int) {
if strength > amount {
self.strength -= amount
}
}
mutating func defineImage(status: MonsterStatus, slash: Bool) {
self.status = status
switch status {
case .Normal: imagePic = images.Normal
if playSound {
Sound.play(file: self.soundAppear)
}
case .Attack: imagePic = images.Attack
if playSound {
Sound.play(file: self.soundAttack)
}
case .Defend: imagePic = images.Defend
if playSound {
Sound.play(file: self.soundDefend)
}
case .Hit : imagePic = images.Hit
if playSound {
Sound.play(file: self.soundHit)
}
case .Dead : imagePic = images.Dead
if playSound {
Sound.play(file: self.soundDead)
}
}
self.slash = slash
}
mutating func nextImage() {
switch self.status {
case .Normal: defineImage(status: .Attack, slash: false)
case .Attack: defineImage(status: .Defend, slash: false)
case .Defend: defineImage(status: .Hit, slash: false)
case .Hit : defineImage(status: .Dead, slash: false)
case .Dead : defineImage(status: .Normal, slash: false)
}
}
mutating func setID(_ id: Int) {
self.id = id
}
mutating func reduceLife() {
life -= 1
}
mutating func addLife() {
life += 1
}
func provideLife() -> String {
var lifeString = ""
for _ in 0..<life {
lifeString.append("💜")
}
return lifeString
}
///
private enum CodingKeys: String, CodingKey {
case id
case name
case imagePrefix
case strength
case life
case fleeChance
case isBoss
case flying
case mage
case venomous
case giant
case carnivore
case herbivore
case type
case location
case soundAppear
case soundHit
case soundDefend
case soundAttack
case soundDead
case images
case scaleFactor
case description
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
id = try container.decode(Int.self, forKey: .id)
name = try container.decode(String.self, forKey: .name)
imagePrefix = try container.decode(String.self, forKey: .imagePrefix)
strength = try container.decode(Int.self, forKey: .strength)
life = try container.decode(Int.self, forKey: .life)
fleeChance = try container.decode(Int.self, forKey: .fleeChance)
isBoss = try container.decode(Bool.self, forKey: .isBoss)
flying = try container.decode(Bool.self, forKey: .flying)
mage = try container.decode(Bool.self, forKey: .mage)
venomous = try container.decode(Bool.self, forKey: .venomous)
giant = try container.decode(Bool.self, forKey: .giant)
carnivore = try container.decode(Bool.self, forKey: .carnivore)
herbivore = try container.decode(Bool.self, forKey: .herbivore)
let monsterTypeValue = try container.decode(String.self, forKey: .type)
type = MonsterType(rawValue: monsterTypeValue) ?? .Error
let locationValue = try container.decode(String.self, forKey: .location)
location = HordeGameData.Location(rawValue: locationValue) ?? .Error
soundAppear = try container.decode(String.self, forKey: .soundAppear)
soundHit = try container.decode(String.self, forKey: .soundHit)
soundDefend = try container.decode(String.self, forKey: .soundDefend)
soundAttack = try container.decode(String.self, forKey: .soundAttack)
soundDead = try container.decode(String.self, forKey: .soundDead)
images = .init(Normal: imagePrefix + "Normal",
Attack: imagePrefix + "Attack",
Defend: imagePrefix + "Defend",
Hit: imagePrefix + "Hit",
Dead: imagePrefix + "Dead")
imagePic = imagePrefix + "Normal"
// let stringScale = try container.decode(CGFloat.self, forKey: .scaleFactor)
// print(stringScale)
// scaleFactor = stringScale.CGFloatValue() ?? 0.5
description = try container.decode(String.self, forKey: .description)
print("Monster \(id): \(name) imported")
}
}
Decode funtion
class MonsterInventory {
static let shared = MonsterInventory()
var staticItems: [Monster] = []
private init() {
self.parseMonsterJson()
}
private func parseMonsterJson() {
guard let monsterJsonFileUrl = Bundle.main.url(forResource: "Monsters", withExtension: "json") else {
fatalError("Unable to find file")
}
do {
let content = try Data(contentsOf: monsterJsonFileUrl)
let jsonDecoder = JSONDecoder()
staticItems = try jsonDecoder.decode([Monster].self, from: content)
} catch let error {
print(error.localizedDescription)
staticItems = []
}
}
func fetchMonsterWithID(_ id: Int) -> Monster {
return staticItems.filter{$0.id == id}.first ?? staticItems.filter{$0.id == 10}.first!
}
func fetchMonsterWithName(_ name: String) -> Monster {
return staticItems.filter{$0.name == name}.first ?? staticItems.filter{$0.id == 10}.first!
}
}
here is an excerpt of the JSON:
[
{
"id": 1,
"name": "Skeleton Warrior",
"strength": 6,
"life": 1,
"fleeChance": 0,
"isBoss": false,
"flying": false,
"mage": false,
"venomous": false,
"giant": false,
"carnivore": false,
"herbivore": false,
"type": "Undead",
"location": "Plains",
"soundAppear": "skeletonWalk4.mp4",
"soundHit": "skeletonHit1.mp3",
"soundDefend": "swordSwoosh2.m4a",
"soundAttack": "swordSwoosh1.m4a",
"soundDead": "skeletonShatter2.mp3",
"imagePrefix": "SkeletonWarrior",
"imageNormal": "SkeletonWarriorNormal",
"imageAttack": "SkeletonWarriorAttack",
"imageDefend": "SkeletonWarriorDefend",
"imageHit": "SkeletonWarriorHit",
"imageDead": "SkeletonWarriorDead",
"scaleFactor": 1.1,
"description": "A weak skeleton fighter."
},
...
]
I'm unsure why are you decoding key by key... I think you should create a struct unless you really need to decode key by key (like
flanker mentioned in a comment to this answer).
If you do need to go key by key you need to provide more details on how are you decoding the JSON
This is the struct that fits your JSON
struct Elm: Codable {
var id: Int
var string: String
var int: Int
var cgfloat: CGFloat // also can be Double and Float
}
You can also change the variables names to follow the camelCase naming.
struct Elm: Codable {
var id: Int
var string: String
var int: Int
var cgFloat: Double
private enum CodingKeys : String, CodingKey {
case id, string, int, cgFloat = "cgfloat"
}
}
This is how you can decode the object.
let decoder = JSONDecoder()
let obj = try! decoder.decode([Elm].self, from: json.data(using: .utf8)!)
In this case the root of your JSON is an array so for that case instead of using Elm.self we use [Elm].self to decode to the array
This way Swift maps the JSON Key to the matching variable name (or using the Coding Keys like I demonstrated in the second struct)
This was tested in Playgrounds
UPDATE 1
Based on your updated question I now see why the individual decodings, but assuming that the key that you are having problems with is the scaleFactor which is the only Float number in the sample the code you provided works with no issues at all.
// [...]
// uncommented your code
scaleFactor = try container.decode(CGFloat.self, forKey: .scaleFactor)
let _scaleFactor = try container.decode(CGFloat.self, forKey: .scaleFactor) // both work
description = try container.decode(String.self, forKey: .description)
print("Monster \(id): \(name) imported")
Now you can double check if the reading of the file is being done correctly (if you can read the rest of the file it probably is)
Related
Something must be wrong in my model because my project fails with this error whenever this function runs.
func communityGroupPost(postUtil: PostShareUtil, success: #escaping (ResultModel<BaseModel>) -> Void, fail: #escaping (ErrorModel) -> Void) {
let url = "CommunityGroup/CommunityGroupPost"
manager.post(url, bodyParameters: postUtil.toDictionary(), success: success, fail: fail).setJsonKey(nil).fetch()
}
The problem says Rtuk.SurveyAnswer but I could not understand what is wrong with SurveyAnswer model. I am appending surveyAnswers for all Json values.
Thanks in advance for the help.
Any ideas?
import Foundation
import Networking
import SwiftyJSON
class SurveySummary: Serializable {
var question: String?
var surveyAnswers: [SurveyAnswer]?
var surveyTypeId: Int?
var order: Int!
init(question: String, surveyAnswers: [SurveyAnswer], surveyTypeId: Int, order: Int) {
self.question = question
self.surveyAnswers = surveyAnswers
self.surveyTypeId = surveyTypeId
self.order = order
}
enum CodingKeys: String, CodingKey {
case question
case surveyAnswers
case surveyTypeId
case order
}
required init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
question = try values.decodeIfPresent(String.self, forKey: .question)
surveyAnswers = try values.decodeIfPresent([SurveyAnswer].self, forKey: .surveyAnswers)
surveyTypeId = try values.decodeIfPresent(Int.self, forKey: .surveyTypeId)
order = try values.decodeIfPresent(Int.self, forKey: .order)
}
func encode(to encoder: Encoder) throws {
var values = encoder.container(keyedBy: CodingKeys.self)
try values.encodeIfPresent(question, forKey: .question)
try values.encodeIfPresent(surveyAnswers, forKey: .surveyAnswers)
try values.encodeIfPresent(surveyTypeId, forKey: .surveyTypeId)
try values.encodeIfPresent(order, forKey: .order)
}
init(withJSON json: JSON) {
question = json["question"].string
surveyAnswers = []
json["surveyAnswers"].arrayValue.forEach { json in
self.surveyAnswers?.append(SurveyAnswer(withJSON: json))
}
surveyTypeId = json["surveyTypeId"].intValue
order = json["order"].intValue
}
}
extension SurveySummary {
func convertToDictionary() -> NSDictionary {
let dictionary = NSMutableDictionary()
dictionary["question"] = question
dictionary["surveyTypeId"] = surveyTypeId
dictionary["surveyAnswers"] = surveyAnswers
dictionary["order"] = order
return dictionary
}
}
And this is my PostShareUtil
final class PostShareUtil {
// MARK: - Properties
var surveys: [SurveySummary]?
var pinnedUntilAsTimestamp: Double?
var postType: Int?
// MARK: - Survey Create
init(group: CommunityGroupModel?, postDesc: String, surveys: [SurveySummary], setting: PostShareSetting, pinnedUntilAsTimestamp: Double, postType: Int) {
self.group = group
self.postDesc = postDesc
self.surveys = surveys
self.setting = setting
self.pinnedUntilAsTimestamp = pinnedUntilAsTimestamp
self.postType = postType
}
The problem was in convertToDictionary func for surveyAnswers. If changed to below code it works fine
extension SurveySummary {
func convertToDictionary() -> NSDictionary {
let dictionary = NSMutableDictionary()
dictionary["question"] = question
dictionary["surveyTypeId"] = surveyTypeId
if surveyAnswers != nil {
let jsonDict = surveyAnswers!.map { $0.convertToDictionary() }
dictionary["surveyAnswers"] = jsonDict
}
dictionary["order"] = order
return dictionary
}
}
I have a question/answer app with local JSON. I want to update JSON with new questions for the table view and delete the duplicate questions that emerge in the realm database with each app reload and keep the new questions.
Based on a previously answered question, I implemented the strategy below, which provides the tableview with single question objects. I use a uuid as the primary key and each question also has an individual questionid. With each reload, a new uuid is made for each question causing duplicates in the realm database. I would like to keep only new question items upon reloading app while deleting the duplicates. I have an example of my database below too showing what is happening.
JSON Maintenance Functions within App Delegate:
//MARK: - Read Local JSON File
func readLocalJSONFile(forName name: String) -> Data? {
do {
if let filePath = Bundle.main.path(forResource: name, ofType: "json") {
let fileUrl = URL(fileURLWithPath: filePath)
let data = try Data(contentsOf: fileUrl)
return data
}
} catch {
print("error: \(error)")
}
return nil
}
//MARK: - Parse JSON
func parseJSON(jsonData: Data){
do {
let decoder = JSONDecoder()
let result = try decoder.decode([Question].self, from: jsonData)
if realm.object(ofType: Question.self, forPrimaryKey: "_id" ) != nil{
} else{
try realm.write{
for question in result{
let questionClass = Question()
questionClass._id = question._id
questionClass.questionid = question.questionid
questionClass.date = question.date
questionClass.text = question.text
questionClass.answer1 = question.answer1
questionClass.answer2 = question.answer2
questionClass.answer3 = question.answer3
questionClass.answer4 = question.answer4
questionClass.score = question.score
questionClass.color = question.color
self.realm.add(questionClass, update: .modified)
}
}
}
}catch {
print(error)
}
}
//MARK: - Data Manipulation Methods
func loadProfiles(){
if let localData = readLocalJSONFile(forName: K.minketClues) {
parseJSON(jsonData: localData)
}
func deleteAllRealm() {
try! realm.write {
realm.deleteAll()
}
}
}
loadProfiles()
Question Model
class Question: Object, Decodable{
#objc dynamic var _id: String = UUID().uuidString
#objc dynamic var questionid: Int = 0
#objc dynamic var date: String = ""
#objc dynamic var text: String = ""
#objc dynamic var answer1: String = ""
#objc dynamic var answer2: String = ""
#objc dynamic var answer3: String = ""
#objc dynamic var answer4: String = ""
#objc dynamic var score: Bool = false
#objc dynamic var color: String = "#30336b"
let answers = List<Answers>()
private enum CodingKeys: String, CodingKey { case _id,questionid,date,text,answer1,answer2,answer3,answer4}
required init(from decoder: Decoder) throws{
let container = try decoder.container(keyedBy: CodingKeys.self)
// _id = try container.decode(Int.self, forKey: ._id)
questionid = try container.decode(Int.self, forKey: .questionid)
date = try container.decode(String.self, forKey: .date)
text = try container.decode(String.self, forKey: .text)
answer1 = try container.decode(String.self, forKey: .answer1)
answer2 = try container.decode(String.self, forKey: .answer2)
answer3 = try container.decode(String.self, forKey: .answer3)
answer4 = try container.decode(String.self, forKey: .answer4)
super.init()
}
override static func primaryKey() -> String?
{
return "_id"
}
required override init()
{
super.init()
}
required init(value: Any, schema: RLMSchema)
{
super.init()
}
required init(realm: RLMRealm, schema: RLMObjectSchema)
{
super.init()
}
}
UPDATE/POTENTIAL SOLUTION:
func parseJSON(jsonData: Data){
do {
let decoder = JSONDecoder()
let result = try decoder.decode([Question].self, from: jsonData)
for question in result{
let questionClass = Question()
questionClass._id = question._id
questionClass.questionid = question.questionid
questionClass.date = question.date
questionClass.text = question.text
questionClass.answer1 = question.answer1
questionClass.answer2 = question.answer2
questionClass.answer3 = question.answer3
questionClass.answer4 = question.answer4
questionClass.score = question.score
questionClass.color = question.color
if let currentObject = realm.object(ofType:Question.self, forPrimaryKey: question.questionid){
questionClass.score = currentObject.score
questionClass.color = currentObject.color
}
try! realm.write{
realm.add(questionClass, update: .modified)
}
}
}catch {
print(error)
}
}
I have a class defined as:
struct MyObject: Identifiable{
var id = UUID()
var paramA: String
var paramB: String
var paramC: String
init(paramA: String, paramB: String = "default", paramC: String = "tomato" ){
self.paramA = paramA
self.paramB = paramB
self.paramC = paramC
}
}
For read the Json Im using landmarkData
let landmarkData: [Landmark] = load("inputData.json")
func load<T: Decodable>(_ filename: String) -> T {
let data: Data
guard let file = Bundle.main.url(forResource: filename, withExtension: nil)
else {
fatalError("Couldn't find \(filename) in main bundle.")
}
do {
data = try Data(contentsOf: file)
} catch {
fatalError("Couldn't load \(filename) from main bundle:\n\(error)")
}
do {
let decoder = JSONDecoder()
return try decoder.decode(T.self, from: data)
} catch {
fatalError("Couldn't parse \(filename) as \(T.self):\n\(error)")
}
}
I've decided that the data can also come through a Json, so I changed the structure of MyObject (adding Hashable, Codable and changing var id = UUID() for Int and move to parameter)
struct MyObject: **Hashable, Codable**, Identifiable{
**var id = Int**
var paramA: String
var paramB: String
var paramC: String
init(**id : Int,** paramA: String, paramB: String = "default", paramC: String = "tomato" ){
**self.id = id**
self.paramA = paramA
self.paramB = paramB
self.paramC = paramC
}
}
This works with a Json with all the keys on each object. But I would like to not define the keys that will take a default value(paramA and paramB keys). And I don't have to define the id in the Json.
inputData.json
[{
"paramA": "Hello",
"paramB": "World",
"paramC": "!!!",
},
{
"paramA": "Hi",
},
{
"paramA": "Hello",
"paramC": "??",
}]
It worked by adding a decoder int and a try for the cases that could not bring the key. I don't know if there's a better solution.
struct MyObject: Identifiable{
var id = UUID()
var paramA: String
var paramB: String
var paramC: String
init(paramA: String, paramB: String = "default", paramC: String = "tomato" ){
self.paramA = paramA
self.paramB = paramB
self.paramC = paramC
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
name = try values.decode(String.self, forKey: .paramA)
do {
paramB = try values.decode(String.self, forKey: .paramB)
}
catch {
paramB = "default"
}
do {
paramC = try values.decode(String.self, forKey: .paramC)
}
catch {
paramC = "default"
}
}
I have done JSON decoding before, but for some reason I can't figure this out. In the playground, it simply crashes with no explanation so I decided to put it into a single view project to trace the problem.
The initializer isn't being called at all. Tried decoding with the super class and got the same thing. Would love another pair of eyes on this.
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let json = """
{"key": 5, "accountKey": "checking", "amount": 100000, "type": "deposit", "date": "2019-03-05T15:29:32Z", "locationKey", "Payroll", isReconciled: false}
"""
let jsonData = json.data(using: .utf8)!
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601
let transaction = try? decoder.decode(BasicTransaction.self, from: jsonData)
print(transaction?.locationKey)
}
}
Here are the supporting definitions:
enum TransactionType: CaseIterable {
case purchase
case deposit
case ccPayment
}
extension TransactionType: RawRepresentable {
typealias RawValue = String
init(rawValue: Self.RawValue) {
switch rawValue {
case "deposit": self = .deposit
case "payment": self = .ccPayment
default: self = .purchase
}
}
var rawValue: String {
switch self {
case .purchase: return "purchase"
case .deposit: return "deposit"
case .ccPayment: return "payment"
}
}
}
class MoneyTransaction: Decodable {
var key = UUID().uuidString
var type = TransactionType.purchase
var accountKey = ""
var ccAccountKey: String? = nil
var recurringTransactionKey: String? = nil
var isRecurring: Bool { !(recurringTransactionKey ?? "").isEmpty }
var locationKey: String? = nil
var addressKey: String? = nil
var categoryKey: String? = nil
var note: String? = nil
var amount: Double = 0
private enum MoneyTransactionKey: String, CodingKey {
case key, type, accountKey, ccAccountKey, recurringTransactionKey, locationKey, addressKey, categoryKey, note, amount, isNew
}
required init(from decoder: Decoder) throws {
print("initializing MoneyTransaction")
let container = try decoder.container(keyedBy: MoneyTransactionKey.self)
key = try container.decode(String.self, forKey: .key)
accountKey = try container.decode(String.self, forKey: .accountKey)
let rawAmount = try container.decode(Int.self, forKey: .amount)
amount = Double(rawAmount / 100)
type = try TransactionType(rawValue: container.decode(String.self, forKey: .type))
recurringTransactionKey = try container.decodeIfPresent(String.self, forKey: .recurringTransactionKey)
locationKey = try container.decodeIfPresent(String.self, forKey: .locationKey)
categoryKey = try container.decodeIfPresent(String.self, forKey: .categoryKey)
note = try container.decodeIfPresent(String.self, forKey: .note)
ccAccountKey = try container.decodeIfPresent(String.self, forKey: .ccAccountKey)
addressKey = try container.decodeIfPresent(String.self, forKey: .addressKey)
}
}
class BasicTransaction: MoneyTransaction {
var date = Date()
var checkNumber: Int? = nil
var isReconciled = false
private enum BasicTransactionKey: String, CodingKey {
case date, checkNumber, isReconciled
}
required init(from decoder: Decoder) throws {
print("initializing BasicTransaction")
try super.init(from: decoder)
let container = try decoder.container(keyedBy: BasicTransactionKey.self)
date = try container.decode(Date.self, forKey: .date)
checkNumber = try container.decodeIfPresent(Int.self, forKey: .checkNumber)
isReconciled = try container.decode(Bool.self, forKey: .isReconciled)
}
}
The json is incorrect it's like
{
"key": 5,
"accountKey": "checking",
"amount": 100000,
"type": "deposit",
"date": "2019-03-05T15:29:32Z",
"locationKey", << key with no value
"Payroll", << key with no value
isReconciled: false << key with no ""
}
It would be like
{
"key": 5,
"accountKey": "checking",
"amount": 100000,
"type": "deposit",
"date": "2019-03-05T15:29:32Z",
"locationKey":"",
"Payroll":"",
"isReconciled": false
}
then BasicTransaction
class BasicTransaction: Codable {
let key: Int
let accountKey: String
let amount: Int
let type: String
let date: Date
let locationKey, payroll: String
let isReconciled: Bool
enum CodingKeys: String, CodingKey {
case key, accountKey, amount, type, date, locationKey
case payroll = "Payroll"
case isReconciled
}
init(key: Int, accountKey: String, amount: Int, type: String, date: Date, locationKey: String, payroll: String, isReconciled: Bool) {
self.key = key
self.accountKey = accountKey
self.amount = amount
self.type = type
self.date = date
self.locationKey = locationKey
self.payroll = payroll
self.isReconciled = isReconciled
}
}
my problem is the next, I have a this json
{
"nombre" : "userProfile.user.name!",
"apaterno" : 20,
"amaterno" : true,
"email" : 100,
"start_screen" : {
"info" : true,
"title" : false,
"image" : 20,
"success_btn" : "hola",
"close_btn" : true
}
}
i want to pass this json to my struct, my struct is :
struct person: Decodable
{
var email : Int
var nombre : String
var apaterno : Int
var amaterno: Bool
struct start_screen {
var title: Bool
var info: Bool
var image: Int
var success_btn: String
var close_btn: Bool
}
}
with the next lines I achieved put the json in my struct, but start_screen struct can't get the data.
let jsonData = json.data(using: .utf8)!
let decoder = JSONDecoder()
let myStruct = try! decoder.decode(person.self, from: jsonData)
when I access myStruct.email I get 100, its ok, but I can't load the start_screen data, how should I do it?
First you would need to add a variable to person for start_screen.
var start_screen: start_screen
Then you would need to make start_screen Decodable
struct start_screen: Decodable
That should be the minimum amount of changes to get it working.
Additionally, you might want to make your types capitalized. start_screen: start_screen is really confusing looking. You can also make your variable and type names camelCase and have the JSONDecoder convert to/from snake_case for you. It's also the naming convention in swift. It'd look like this
struct Person: Decodable {
var email: Int
var nombre: String
var apaterno: Int
var amaterno: Bool
var startScreen: StartScreen
struct StartScreen: Decodable {
var title: Bool
var info: Bool
var image: Int
var successBtn: String
var closeBtn: Bool
}
}
let jsonData = json.data(using: .utf8)!
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let person = try! decoder.decode(Person.self, from: jsonData)
print(person)
This should be your Person struct :
struct Person: Decodable {
var email : Int?
var nombre : String?
var apaterno : Int?
var amaterno: Bool
var start_screen: Start_screen?
enum CodingKeys: String, CodingKey {
case email = "email"
case nombre = "nombre"
case apaterno = "apaterno"
case amaterno = "amaterno"
case start_screen = "amaterno"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
email = try values.decodeIfPresent(Int.self, forKey: .email)
nombre = try values.decodeIfPresent(String.self, forKey: .nombre)
apaterno = try values.decodeIfPresent(Int.self, forKey: .apaterno)
amaterno = try values.decodeIfPresent(Bool.self, forKey: .apaterno) ?? false
start_screen = try values.decodeIfPresent(Start_screen.self, forKey: .start_screen)
}
}
This should be your Start_screen struct :
struct Start_screen: Decodable {
var title: Bool
var info: Bool
var image: Int?
var success_btn: String?
var close_btn: Bool
enum CodingKeys: String, CodingKey {
case title = "title"
case info = "info"
case image = "image"
case success_btn = "success_btn"
case close_btn = "close_btn"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
title = try values.decodeIfPresent(Bool.self, forKey: .title) ?? false
info = try values.decodeIfPresent(Bool.self, forKey: .info) ?? false
image = try values.decodeIfPresent(Int.self, forKey: .image)
success_btn = try values.decodeIfPresent(String.self, forKey: .success_btn)
close_btn = try values.decodeIfPresent(Bool.self, forKey: .close_btn) ?? false
}
}
Accessing start_screen from Person :
if let jsonData = json.data(using: .utf8) {
let user = try! JSONDecoder().decode(Person.self, from: jsonData)
if let title = user.start_screen.title {
print(title)
}
}