Swift 5 : Create JSON with Codable / Struct from my variables

2.5k views Asked by At

I would like to create a Codable structure to save data from a user profile. Let's say the iOS user filled a form with his name, last name, address, put his picture (UIImage) and that user have a unique ID. My JSON would be like:

{"Unique_ID": "1234556778", 
    {"Name": "John",
     "Last Name": "Doe",
     "adress":
        {"street": "21 jump street",
         "country": "USA"}
     } 
}

I tried to create the Codable but I think it isn't optimal, could you kindly give me some hint to optimize it or tell me if I'm wrong doing it like this ?

struct User_Profile: Codable {
    let Unique_ID: String
    let Data_Of_User: User_Profile_Data
}

struct User_Profile_Data: Codable {
    let name: String
    let last_name: String
    let adress : User_Adress_Data
}

struct User_Adress_Data: Codable {
    let street: String
    let country: String
}

override func viewDidLoad() {
    super.viewDidLoad()
            
    let usernametest = "John"
         
    let b = User_Adress_Data(street: "21 jump street", country: "USA")
    let c = User_Profile_Data(name: usernametest, last_name: "Doe", adress: b)
    let d = User_Profile(Unique_ID: "123456", Data_Of_User: c)
}

After that, I would like to cache it, with Haneke swift (https://github.com/Haneke/HanekeSwift), or Datacache (https://github.com/huynguyencong/DataCache). How can I cache my Codables? (for eg, I didn't view any 'set cache for json' with Haneke)

And finally, I would like to use it after fetched with SwiftyJSON: (https://github.com/SwiftyJSON/SwiftyJSON), and I don't know if my Codables are enought readable for it.

Thanks for your idea/comment !

3

There are 3 answers

2
burnsi On BEST ANSWER

As you have full controll over your structure and there is no collection involved i would recommend to put everything in one struct instead of scattering it over many different:

struct UserProfile: Codable{
    var id: String
    var name: String
    var lastname: String
    var street: String
    var country: String
}

Regarding caching. As you can mark this struct Codable it can be easily stored in Userdefaults as Data. This extension on Userdefaults should allow you to access UserProfile in a typesafe manner.

extension UserDefaults{
    var userProfile: UserProfile?{
        get{
            guard let data = data(forKey: "userProfile") else{
                return nil
            }
            
            return try? JSONDecoder().decode(UserProfile.self, from: data)
        }
        set{
            set(try? JSONEncoder().encode(newValue), forKey: "userProfile")
        }
    }
}

Usage example:

//Write
UserDefaults.standard.userProfile = UserProfile(id: UUID().uuidString, name: "First", lastname: "Last", street: "nowhere", country: "atlantis")
//Read
let profile = UserDefaults.standard.userProfile

Edit: Editing example:

As this is a struct it gets copied every time something changes. So read the value in a var. Modify it and save it to the defaultStore.

var profile = UserDefaults.standard.userProfile
profile?.country = "newCountry"
UserDefaults.standard.userProfile = profile
2
Joshua Kaplan On

The example you've provided isn't valid JSON. It looks like some sort of hybrid of a dictionary and an array.

So let's make a few tweaks:

{
  "Unique_ID": "1234556778",
  "Data_of_User": {
    "First_Name": "John",
    "Last_Name": "Doe",
    "Address": {
      "Street": "21 jump street",
      "Country": "USA"
    }
  }
}

Then you can create structs which look like:

struct UserProfile: Codable {
    let uniqueID: String
    let userData: UserData
    
    enum CodingKeys: String, CodingKey {
        case uniqueID = "Unique_ID"
        case userData = "Data_of_User"
    }
}

struct UserData: Codable {
    let firstName: String
    let lastName: String
    let address: Address
    
    enum CodingKeys: String, CodingKey {
        case firstName = "First_Name"
        case lastName = "Last_Name"
        case address = "Address"
    }
}

struct Address: Codable {
    let street: String
    let country: String
    
    enum CodingKeys: String, CodingKey {
        case street = "Street"
        case country = "Country"
    }
}
0
inTheAM On

Codable requires the property names in your struct to match the json counterparts. In your User_Profile_Data struct you have name while the json contains "Name" and you have last_name where the json contains "Last Name".

One way to get around this is to add a CodingKeys enum to your struct that tells it how to map the properties. So in your User_Profile_Data struct I would add:

enum CodingKeys: String, CodingKey { case name = "Name" case last_name = "Last Name" case adress }