Related
import Firebase
import UIKit
//I followed the information you gave me. I am unsure if I have done that correctly or as you were expecting it. But, it gives the same error for codable. "Type post doesn't conform to protocol decodable".
import Firebase
import UIKit
struct Post: Codable {
var caption: String
var likes: Int
var imageUrl: String
var ownerUid: String
var postId: String
var ownerImageUrl: String
var ownerUsername: String
var didLike = false
var hashtags: [String]
var activity: [String]
var video: String
var videoURL: URL
var videoFileExtension: String?
var music: String
private var timestampDate: Date
var timestamp: Timestamp { Timestamp(date: timestampDate) }?
enum CodingKeys: String, CodingKey {
case caption
case likes
case imageUrl
case ownerUid
case timestamp
case postId
case ownerImageUrl
case ownerUsername
case didLike
case hashtags
case activity
case video
case videoURL
case videoFileExtension
case music
}
init(postId: String, dictionary: [String: Any]) {
self.postId = dictionary["postId"] as? String ?? ""
self.caption = dictionary["caption"] as? String ?? ""
self.likes = dictionary["likes"] as? Int ?? 0
self.imageUrl = dictionary["imageUrl"] as? String ?? ""
self.ownerUid = dictionary["ownerUid"] as? String ?? ""
self.ownerImageUrl = dictionary["ownerImageUrl"] as? String ?? ""
self.ownerUsername = dictionary["ownerUsername"] as? String ?? ""
self.hashtags = dictionary["hashtags"] as? [String] ?? [String]()
self.activity = dictionary["activity"] as? [String] ?? [String]()
self.video = dictionary["video"] as? String ?? ""
self.videoURL = dictionary["videoURL"] as? URL ?? URL(fileURLWithPath: "")
self.music = dictionary["music"] as? String ?? ""
if let asDouble = dictionary["timestamp"] as? Double { self.timestampDate = Date(timeIntervalSince1970: asDouble) } else { self.timestampDate = Date() }
}
//Here I am using JSONEncoder to be called in other parts of the code and to //help process the data to firebase
var dictionary: [String: Any] {
let data = (try? JSONEncoder().encode(self)) ?? Data()
return (try? JSONSerialization.jsonObject(with: data, options: [.mutableContainers, .allowFragments]) as? [String: Any]) ?? [:]
}
}
The compiler will not synthesize Codable for you since you have a coding key for a computed property. This is not supported, auto synthesis only works with stored properties. If you remove timestamp from your CodingKeys enum it should work fine, but your encoded JSON won’t contain the timestamp. If you need that in your output or parse it from input you will have to implement Codable yourself.
Upon the initial question:
A way to do that would be to keep the Date as private, and use Timestamp as a computed value:
private var timestampDate: Date
var timestamp: Timestamp { Timestamp(date: timestampDate) }
This need a little changes in the CodingKeys, because timestamp doesn't exists for it, but timestampDate does now:
enum CodingKeys: String, CodingKey {
...
case timestampDate = "timestamp"
}
Now, there are still a few issues.
self.videoURL = dictionary["videoURL"] as? URL ?? URL(fileURLWithPath: "")
This shouldn't work, since you are getting JSON, and URL isn't really a JSON value.
Instead:
let videoURLString = dictionary["videoURL"] as? String ?? ""
self.videoURL = URL(fileURLWithPath: videoURLString)
Now, you might have an issue with the Date value, you need to tell the encoder what's the logic:
var dictionary: [String: Any] {
do {
let encoder = JSONEncoder()
encoder.dateEncodingStrategy = .secondsSince1970
let data = try encoder.encode(self)
let dict = try JSONSerialization.jsonObject(with: data)
return dict as? [String: Any] ?? [:]
} catch {
print("Error: \(error)")
return [:]
}
}
I used as reference date 1970, depending on your settings, you might change it when encoding or decoding.
Also, I did proper do/try/catch, please don't write try?. If there is an error, you won't see it, you are just ignoring them.
Now, it's unrelated, but in the init(postId:dictionary:) you don't read postId value. Did you meant self.postId = dictionary["postId"] as? String ?? postId ?
Instead of using dictionary["someValue"], why not use dictionary[CodingKeys.someValue.rawValue], avoiding you any typo error?
Swift 4 introduced support for native JSON encoding and decoding via the Decodable protocol. How do I use custom keys for this?
E.g., say I have a struct
struct Address:Codable {
var street:String
var zip:String
var city:String
var state:String
}
I can encode this to JSON.
let address = Address(street: "Apple Bay Street", zip: "94608", city: "Emeryville", state: "California")
if let encoded = try? encoder.encode(address) {
if let json = String(data: encoded, encoding: .utf8) {
// Print JSON String
print(json)
// JSON string is
{ "state":"California",
"street":"Apple Bay Street",
"zip":"94608",
"city":"Emeryville"
}
}
}
I can encode this back to an object.
let newAddress: Address = try decoder.decode(Address.self, from: encoded)
But If I had a json object that was
{
"state":"California",
"street":"Apple Bay Street",
"zip_code":"94608",
"city":"Emeryville"
}
How would I tell the decoder on Address that zip_code maps to zip? I believe you use the new CodingKey protocol, but I can't figure out how to use this.
Manually customising coding keys
In your example, you're getting an auto-generated conformance to Codable as all your properties also conform to Codable. This conformance automatically creates a key type that simply corresponds to the property names – which is then used in order to encode to/decode from a single keyed container.
However one really neat feature of this auto-generated conformance is that if you define a nested enum in your type called "CodingKeys" (or use a typealias with this name) that conforms to the CodingKey protocol – Swift will automatically use this as the key type. This therefore allows you to easily customise the keys that your properties are encoded/decoded with.
So what this means is you can just say:
struct Address : Codable {
var street: String
var zip: String
var city: String
var state: String
private enum CodingKeys : String, CodingKey {
case street, zip = "zip_code", city, state
}
}
The enum case names need to match the property names, and the raw values of these cases need to match the keys that you're encoding to/decoding from (unless specified otherwise, the raw values of a String enumeration will the same as the case names). Therefore, the zip property will now be encoded/decoded using the key "zip_code".
The exact rules for the auto-generated Encodable/Decodable conformance are detailed by the evolution proposal (emphasis mine):
In addition to automatic CodingKey requirement synthesis for
enums, Encodable & Decodable requirements can be automatically
synthesized for certain types as well:
Types conforming to Encodable whose properties are all Encodable get an automatically generated String-backed CodingKey enum mapping
properties to case names. Similarly for Decodable types whose
properties are all Decodable
Types falling into (1) — and types which manually provide a CodingKey enum (named CodingKeys, directly, or via a typealias) whose
cases map 1-to-1 to Encodable/Decodable properties by name — get
automatic synthesis of init(from:) and encode(to:) as appropriate,
using those properties and keys
Types which fall into neither (1) nor (2) will have to provide a custom key type if needed and provide their own init(from:) and
encode(to:), as appropriate
Example encoding:
import Foundation
let address = Address(street: "Apple Bay Street", zip: "94608",
city: "Emeryville", state: "California")
do {
let encoded = try JSONEncoder().encode(address)
print(String(decoding: encoded, as: UTF8.self))
} catch {
print(error)
}
//{"state":"California","street":"Apple Bay Street","zip_code":"94608","city":"Emeryville"}
Example decoding:
// using the """ multi-line string literal here, as introduced in SE-0168,
// to avoid escaping the quotation marks
let jsonString = """
{"state":"California","street":"Apple Bay Street","zip_code":"94608","city":"Emeryville"}
"""
do {
let decoded = try JSONDecoder().decode(Address.self, from: Data(jsonString.utf8))
print(decoded)
} catch {
print(error)
}
// Address(street: "Apple Bay Street", zip: "94608",
// city: "Emeryville", state: "California")
Automatic snake_case JSON keys for camelCase property names
In Swift 4.1, if you rename your zip property to zipCode, you can take advantage of the key encoding/decoding strategies on JSONEncoder and JSONDecoder in order to automatically convert coding keys between camelCase and snake_case.
Example encoding:
import Foundation
struct Address : Codable {
var street: String
var zipCode: String
var city: String
var state: String
}
let address = Address(street: "Apple Bay Street", zipCode: "94608",
city: "Emeryville", state: "California")
do {
let encoder = JSONEncoder()
encoder.keyEncodingStrategy = .convertToSnakeCase
let encoded = try encoder.encode(address)
print(String(decoding: encoded, as: UTF8.self))
} catch {
print(error)
}
//{"state":"California","street":"Apple Bay Street","zip_code":"94608","city":"Emeryville"}
Example decoding:
let jsonString = """
{"state":"California","street":"Apple Bay Street","zip_code":"94608","city":"Emeryville"}
"""
do {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let decoded = try decoder.decode(Address.self, from: Data(jsonString.utf8))
print(decoded)
} catch {
print(error)
}
// Address(street: "Apple Bay Street", zipCode: "94608",
// city: "Emeryville", state: "California")
One important thing to note about this strategy however is that it won't be able to round-trip some property names with acronyms or initialisms which, according to the Swift API design guidelines, should be uniformly upper or lower case (depending on the position).
For example, a property named someURL will be encoded with the key some_url, but on decoding, this will be transformed to someUrl.
To fix this, you'll have to manually specify the coding key for that property to be string that the decoder expects, e.g someUrl in this case (which will still be transformed to some_url by the encoder):
struct S : Codable {
private enum CodingKeys : String, CodingKey {
case someURL = "someUrl", someOtherProperty
}
var someURL: String
var someOtherProperty: String
}
(This doesn't strictly answer your specific question, but given the canonical nature of this Q&A, I feel it's worth including)
Custom automatic JSON key mapping
In Swift 4.1, you can take advantage of the custom key encoding/decoding strategies on JSONEncoder and JSONDecoder, allowing you to provide a custom function to map coding keys.
The function you provide takes a [CodingKey], which represents the coding path for the current point in encoding/decoding (in most cases, you'll only need to consider the last element; that is, the current key). The function returns a CodingKey that will replace the last key in this array.
For example, UpperCamelCase JSON keys for lowerCamelCase property names:
import Foundation
// wrapper to allow us to substitute our mapped string keys.
struct AnyCodingKey : CodingKey {
var stringValue: String
var intValue: Int?
init(_ base: CodingKey) {
self.init(stringValue: base.stringValue, intValue: base.intValue)
}
init(stringValue: String) {
self.stringValue = stringValue
}
init(intValue: Int) {
self.stringValue = "\(intValue)"
self.intValue = intValue
}
init(stringValue: String, intValue: Int?) {
self.stringValue = stringValue
self.intValue = intValue
}
}
extension JSONEncoder.KeyEncodingStrategy {
static var convertToUpperCamelCase: JSONEncoder.KeyEncodingStrategy {
return .custom { codingKeys in
var key = AnyCodingKey(codingKeys.last!)
// uppercase first letter
if let firstChar = key.stringValue.first {
let i = key.stringValue.startIndex
key.stringValue.replaceSubrange(
i ... i, with: String(firstChar).uppercased()
)
}
return key
}
}
}
extension JSONDecoder.KeyDecodingStrategy {
static var convertFromUpperCamelCase: JSONDecoder.KeyDecodingStrategy {
return .custom { codingKeys in
var key = AnyCodingKey(codingKeys.last!)
// lowercase first letter
if let firstChar = key.stringValue.first {
let i = key.stringValue.startIndex
key.stringValue.replaceSubrange(
i ... i, with: String(firstChar).lowercased()
)
}
return key
}
}
}
You can now encode with the .convertToUpperCamelCase key strategy:
let address = Address(street: "Apple Bay Street", zipCode: "94608",
city: "Emeryville", state: "California")
do {
let encoder = JSONEncoder()
encoder.keyEncodingStrategy = .convertToUpperCamelCase
let encoded = try encoder.encode(address)
print(String(decoding: encoded, as: UTF8.self))
} catch {
print(error)
}
//{"Street":"Apple Bay Street","City":"Emeryville","State":"California","ZipCode":"94608"}
and decode with the .convertFromUpperCamelCase key strategy:
let jsonString = """
{"Street":"Apple Bay Street","City":"Emeryville","State":"California","ZipCode":"94608"}
"""
do {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromUpperCamelCase
let decoded = try decoder.decode(Address.self, from: Data(jsonString.utf8))
print(decoded)
} catch {
print(error)
}
// Address(street: "Apple Bay Street", zipCode: "94608",
// city: "Emeryville", state: "California")
With Swift 4.2, according to your needs, you may use one of the 3 following strategies in order to make your model objects custom property names match your JSON keys.
#1. Using custom coding keys
When you declare a struct that conforms to Codable (Decodable and Encodable protocols) with the following implementation...
struct Address: Codable {
var street: String
var zip: String
var city: String
var state: String
}
... the compiler automatically generates a nested enum that conforms to CodingKey protocol for you.
struct Address: Codable {
var street: String
var zip: String
var city: String
var state: String
// compiler generated
private enum CodingKeys: String, CodingKey {
case street
case zip
case city
case state
}
}
Therefore, if the keys used in your serialized data format don't match the property names from your data type, you can manually implement this enum and set the appropriate rawValue for the required cases.
The following example shows how to do:
import Foundation
struct Address: Codable {
var street: String
var zip: String
var city: String
var state: String
private enum CodingKeys: String, CodingKey {
case street
case zip = "zip_code"
case city
case state
}
}
Encode (replacing zip property with "zip_code" JSON key):
let address = Address(street: "Apple Bay Street", zip: "94608", city: "Emeryville", state: "California")
let encoder = JSONEncoder()
if let jsonData = try? encoder.encode(address), let jsonString = String(data: jsonData, encoding: .utf8) {
print(jsonString)
}
/*
prints:
{"state":"California","street":"Apple Bay Street","zip_code":"94608","city":"Emeryville"}
*/
Decode (replacing "zip_code" JSON key with zip property):
let jsonString = """
{"state":"California","street":"Apple Bay Street","zip_code":"94608","city":"Emeryville"}
"""
let decoder = JSONDecoder()
if let jsonData = jsonString.data(using: .utf8), let address = try? decoder.decode(Address.self, from: jsonData) {
print(address)
}
/*
prints:
Address(street: "Apple Bay Street", zip: "94608", city: "Emeryville", state: "California")
*/
#2. Using snake case to camel case key coding strategies
If your JSON has snake-cased keys and you want to convert them to camel-cased properties for your model object, you can set your JSONEncoder's keyEncodingStrategy and JSONDecoder's keyDecodingStrategy properties to .convertToSnakeCase.
The following example shows how to do:
import Foundation
struct Address: Codable {
var street: String
var zipCode: String
var cityName: String
var state: String
}
Encode (converting camel cased properties into snake cased JSON keys):
let address = Address(street: "Apple Bay Street", zipCode: "94608", cityName: "Emeryville", state: "California")
let encoder = JSONEncoder()
encoder.keyEncodingStrategy = .convertToSnakeCase
if let jsonData = try? encoder.encode(address), let jsonString = String(data: jsonData, encoding: .utf8) {
print(jsonString)
}
/*
prints:
{"state":"California","street":"Apple Bay Street","zip_code":"94608","city_name":"Emeryville"}
*/
Decode (converting snake cased JSON keys into camel cased properties):
let jsonString = """
{"state":"California","street":"Apple Bay Street","zip_code":"94608","city_name":"Emeryville"}
"""
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
if let jsonData = jsonString.data(using: .utf8), let address = try? decoder.decode(Address.self, from: jsonData) {
print(address)
}
/*
prints:
Address(street: "Apple Bay Street", zipCode: "94608", cityName: "Emeryville", state: "California")
*/
#3. Using custom key coding strategies
If necessary, JSONEncoder and JSONDecoder allow you to set a custom strategy to map coding keys using JSONEncoder.KeyEncodingStrategy.custom(_:) and JSONDecoder.KeyDecodingStrategy.custom(_:).
The following example shows how to implement them:
import Foundation
struct Address: Codable {
var street: String
var zip: String
var city: String
var state: String
}
struct AnyKey: CodingKey {
var stringValue: String
var intValue: Int?
init?(stringValue: String) {
self.stringValue = stringValue
}
init?(intValue: Int) {
self.stringValue = String(intValue)
self.intValue = intValue
}
}
Encode (converting lowercased first letter properties into uppercased first letter JSON keys):
let address = Address(street: "Apple Bay Street", zip: "94608", city: "Emeryville", state: "California")
let encoder = JSONEncoder()
encoder.keyEncodingStrategy = .custom({ (keys) -> CodingKey in
let lastKey = keys.last!
guard lastKey.intValue == nil else { return lastKey }
let stringValue = lastKey.stringValue.prefix(1).uppercased() + lastKey.stringValue.dropFirst()
return AnyKey(stringValue: stringValue)!
})
if let jsonData = try? encoder.encode(address), let jsonString = String(data: jsonData, encoding: .utf8) {
print(jsonString)
}
/*
prints:
{"Zip":"94608","Street":"Apple Bay Street","City":"Emeryville","State":"California"}
*/
Decode (converting uppercased first letter JSON keys into lowercased first letter properties):
let jsonString = """
{"State":"California","Street":"Apple Bay Street","Zip":"94608","City":"Emeryville"}
"""
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .custom({ (keys) -> CodingKey in
let lastKey = keys.last!
guard lastKey.intValue == nil else { return lastKey }
let stringValue = lastKey.stringValue.prefix(1).lowercased() + lastKey.stringValue.dropFirst()
return AnyKey(stringValue: stringValue)!
})
if let jsonData = jsonString.data(using: .utf8), let address = try? decoder.decode(Address.self, from: jsonData) {
print(address)
}
/*
prints:
Address(street: "Apple Bay Street", zip: "94608", city: "Emeryville", state: "California")
*/
Sources:
Apple developer documentation: "Encoding and Decoding Custom Types"
WWDC 2017 session 212: "What's new in Foundation"
MartianCraft: "Implementing a custom key strategy for coding types"
What I have done is create own structure just like what you are getting from the JSON with respect to its data types.
Just like this:
struct Track {
let id : Int
let contributingArtistNames:String
let name : String
let albumName :String
let copyrightP:String
let copyrightC:String
let playlistCount:Int
let trackPopularity:Int
let playlistFollowerCount:Int
let artistFollowerCount : Int
let label : String
}
After this you need to create an extension of the same struct extending decodable and the enum of the same structure with CodingKey and then you need to initialize the decoder using this enum with its keys and datatypes (Keys will come from the enum and the datatypes will be coming or say referenced from the structure itself)
extension Track: Decodable {
enum TrackCodingKeys: String, CodingKey {
case id = "id"
case contributingArtistNames = "primaryArtistsNames"
case spotifyId = "spotifyId"
case name = "name"
case albumName = "albumName"
case albumImageUrl = "albumImageUrl"
case copyrightP = "copyrightP"
case copyrightC = "copyrightC"
case playlistCount = "playlistCount"
case trackPopularity = "trackPopularity"
case playlistFollowerCount = "playlistFollowerCount"
case artistFollowerCount = "artistFollowers"
case label = "label"
}
init(from decoder: Decoder) throws {
let trackContainer = try decoder.container(keyedBy: TrackCodingKeys.self)
if trackContainer.contains(.id){
id = try trackContainer.decode(Int.self, forKey: .id)
}else{
id = 0
}
if trackContainer.contains(.contributingArtistNames){
contributingArtistNames = try trackContainer.decode(String.self, forKey: .contributingArtistNames)
}else{
contributingArtistNames = ""
}
if trackContainer.contains(.spotifyId){
spotifyId = try trackContainer.decode(String.self, forKey: .spotifyId)
}else{
spotifyId = ""
}
if trackContainer.contains(.name){
name = try trackContainer.decode(String.self, forKey: .name)
}else{
name = ""
}
if trackContainer.contains(.albumName){
albumName = try trackContainer.decode(String.self, forKey: .albumName)
}else{
albumName = ""
}
if trackContainer.contains(.albumImageUrl){
albumImageUrl = try trackContainer.decode(String.self, forKey: .albumImageUrl)
}else{
albumImageUrl = ""
}
if trackContainer.contains(.copyrightP){
copyrightP = try trackContainer.decode(String.self, forKey: .copyrightP)
}else{
copyrightP = ""
}
if trackContainer.contains(.copyrightC){
copyrightC = try trackContainer.decode(String.self, forKey: .copyrightC)
}else{
copyrightC = ""
}
if trackContainer.contains(.playlistCount){
playlistCount = try trackContainer.decode(Int.self, forKey: .playlistCount)
}else{
playlistCount = 0
}
if trackContainer.contains(.trackPopularity){
trackPopularity = try trackContainer.decode(Int.self, forKey: .trackPopularity)
}else{
trackPopularity = 0
}
if trackContainer.contains(.playlistFollowerCount){
playlistFollowerCount = try trackContainer.decode(Int.self, forKey: .playlistFollowerCount)
}else{
playlistFollowerCount = 0
}
if trackContainer.contains(.artistFollowerCount){
artistFollowerCount = try trackContainer.decode(Int.self, forKey: .artistFollowerCount)
}else{
artistFollowerCount = 0
}
if trackContainer.contains(.label){
label = try trackContainer.decode(String.self, forKey: .label)
}else{
label = ""
}
}
}
You need to change here each and every key and datatypes according to your needs and use it with the decoder.
By using CodingKey you can use custom keys in codable or decodable protocol.
struct person: Codable {
var name: String
var age: Int
var street: String
var state: String
private enum CodingKeys: String, CodingKey {
case name
case age
case street = "Street_name"
case state
} }
In myscenario, I am trying to get the data from JSON response with help of Stuct Codable and Decodable. But I am getting below error If I use below code
Error:typeMismatch(Swift.Dictionary<Swift.String, Any>, Swift.DecodingError.Context(codingPath: [], debugDescription: "Expected to decode Dictionary<String, Any> but found an array instead.", underlyingError: nil))
Code Below
func jsonDataLoad() {
if let url = URL(string: "https://api.myjson.com/bins/1e33oo") {
URLSession.shared.dataTask(with: url) { data, response, error in
if let data = data {
do {
//Swift 2/3/Objective C
//let json = try JSONSerialization.jsonObject(with: data, options: .mutableLeaves)
//print(json)
let student = try JSONDecoder().decode(RootElement.self, from: data)
print(student.gender)
} catch let error {
print(error)
}
}
}.resume()
}
}
Codable Structure
// MARK: - RootElement
struct RootElement: Decodable {
let id, name, gender, welcomeClass: String
let club, persona, crush, breastSize: String
let strength, hairstyle, color: String
let accessory, scheduleTime, scheduleDestination, scheduleAction: String
}
Your json root is an array not a dictionary [RootElement].self
let students = try JSONDecoder().decode([RootElement].self, from: data)
students.forEach {
print($0.id)
}
Follwoing will be the model to parse your JSON, plus i would highly recommend you the following website to create your json models, instead of trying it yourself to avoid mistakes
https://app.quicktype.io/
import Foundation
// MARK: - JSONModelElement
struct JSONModelElement: Codable {
let id, name, gender, jsonModelClass: String
let club, persona, crush, breastSize: String
let strength, hairstyle, color: String
let stockings: Stockings
let accessory, scheduleTime, scheduleDestination, scheduleAction: String
enum CodingKeys: String, CodingKey {
case id = "ID"
case name = "Name"
case gender = "Gender"
case jsonModelClass = "Class"
case club = "Club"
case persona = "Persona"
case crush = "Crush"
case breastSize = "BreastSize"
case strength = "Strength"
case hairstyle = "Hairstyle"
case color = "Color"
case stockings = "Stockings"
case accessory = "Accessory"
case scheduleTime = "ScheduleTime"
case scheduleDestination = "ScheduleDestination"
case scheduleAction = "ScheduleAction"
}
}
enum Stockings: String, Codable {
case loose = "Loose"
case none = "None"
case osana = "Osana"
}
typealias JSONModel = [JSONModelElement]
// pasrse the JSON using one single line.
let students = try JSONDecoder().decode(JSONModel.self, from: data)
You can use this code:
func jsonDataLoad() {
if let url = URL(string: "https://api.myjson.com/bins/1e33oo") {
URLSession.shared.dataTask(with: url) { data, response, error in
if let data = data {
do {
let students = try? JSONDecoder().decode(ModelAPI.self, from: data)
students?.forEach {
print($0.id)
}
}
}
}.resume()
}
}
your class:
import Foundation
class ModelAPIElement: Codable {
let id, name, gender, modelAPIClass: String?
let club, persona, crush, breastSize: String?
let strength, hairstyle, color: String?
let stockings: Stockings?
let accessory, scheduleTime, scheduleDestination, scheduleAction: String?
enum CodingKeys: String, CodingKey {
case id = "ID"
case name = "Name"
case gender = "Gender"
case modelAPIClass = "Class"
case club = "Club"
case persona = "Persona"
case crush = "Crush"
case breastSize = "BreastSize"
case strength = "Strength"
case hairstyle = "Hairstyle"
case color = "Color"
case stockings = "Stockings"
case accessory = "Accessory"
case scheduleTime = "ScheduleTime"
case scheduleDestination = "ScheduleDestination"
case scheduleAction = "ScheduleAction"
}
init(id: String?, name: String?, gender: String?, modelAPIClass: String?, club: String?, persona: String?, crush: String?, breastSize: String?, strength: String?, hairstyle: String?, color: String?, stockings: Stockings?, accessory: String?, scheduleTime: String?, scheduleDestination: String?, scheduleAction: String?) {
self.id = id
self.name = name
self.gender = gender
self.modelAPIClass = modelAPIClass
self.club = club
self.persona = persona
self.crush = crush
self.breastSize = breastSize
self.strength = strength
self.hairstyle = hairstyle
self.color = color
self.stockings = stockings
self.accessory = accessory
self.scheduleTime = scheduleTime
self.scheduleDestination = scheduleDestination
self.scheduleAction = scheduleAction
}
}
enum Stockings: String, Codable {
case loose = "Loose"
case none = "None"
case osana = "Osana"
}
typealias ModelAPI = [ModelAPIElement]
updated: or struct
import Foundation
struct ModelAPIElement: Codable {
let id, name, gender, modelAPIClass: String?
let club, persona, crush, breastSize: String?
let strength, hairstyle, color: String?
let stockings: Stockings?
let accessory, scheduleTime, scheduleDestination, scheduleAction: String?
enum CodingKeys: String, CodingKey {
case id = "ID"
case name = "Name"
case gender = "Gender"
case modelAPIClass = "Class"
case club = "Club"
case persona = "Persona"
case crush = "Crush"
case breastSize = "BreastSize"
case strength = "Strength"
case hairstyle = "Hairstyle"
case color = "Color"
case stockings = "Stockings"
case accessory = "Accessory"
case scheduleTime = "ScheduleTime"
case scheduleDestination = "ScheduleDestination"
case scheduleAction = "ScheduleAction"
}
}
enum Stockings: String, Codable {
case loose = "Loose"
case none = "None"
case osana = "Osana"
}
typealias ModelAPI = [ModelAPIElement]
when a user login, I get an answer from server that contains an array like this:
{"answer":{"id":26,"username":"iosuser","address":"iosios","phone_number":"123123123","email":"gmail#gmail.com","created_at":"2019-02-02 12:42:50","updated_at":"2019-02-02 21:40:29","provider_id":"1","is_loged_in":1,"user_id":"24","user_type":1}}
How to save this array's data in userdefaults and use it later for example in a profile page?
Any help much appreciated.
First don't recommend saving it in userDefault , you can use coredata / realm , and your content is a dictionary that contains an array , but you can do
struct Root: Codable {
let answer: Answer
}
struct Answer: Codable {
let id: Int
let username, address, phoneNumber, email: String
let createdAt, updatedAt, providerID: String
let isLogedIn: Int
let userID: String
let userType: Int
enum CodingKeys: String, CodingKey {
case id, username, address
case phoneNumber = "phone_number"
case email
case createdAt = "created_at"
case updatedAt = "updated_at"
case providerID = "provider_id"
case isLogedIn = "is_loged_in"
case userID = "user_id"
case userType = "user_type"
}
}
UserDefaults.standard.set(data,forKey:"AnyKey")
then read the data and
let res = try? JSONDecoder().decode(Root.self,from:data)
if you need to strip the array only then
do {
let tr = try JSONSerialization.jsonObject(with:data) as! [String:Any]
let arr = tr["answer"] as! [Any]
let ansData = try JSONSerialization.data(withJSONObject:arr, options:[])
UserDefaults.standard.set(ansData,forKey:"AnyKey")
} catch { print(error) }
After that read it like
guard let data = UserDefaults.standard.data(forKey:"AnyKey") else { return }
let res = try? JSONDecoder().decode([Answer].self,from:data)
I have a json response like so…
{
"message_code": 1,
"orderCount": 52,
"productCount": 5,
"outstandingPayment": [],
"pendingOrder": [
{
"order_id": 1,
"grand_total": 67.85
"customer_name": “xcvv”
"mobile_number": 2147483647
},
],
"bestWishes": [
{
"customer_id": 1,
"birth_date": "2018-02-02",
"type": "birth_date",
"customer_name": “xcvv”,
"mobile_number": 2147483647
},
{
"customer_id": 1,
"anniversary_date": "2018-02-02",
"type": "anniversary_date",
"customer_name": “sdfs”,
"mobile_number": 2147483647
}
]
}
To parse pendingOrder I have made a struct like so:
struct PendingOrder: Codable {
let order_id: Int
let grand_total: Double
let customer_name: String
let mobileNo: Int
init(order_id : Int, grand_total: Double, customer_name: String, mobileNo: Int) {
self.order_id = order_id
self.grand_total = grand_total
self.customer_name = customer_name
self.mobileNo = mobileNo
}
}
But how can I make a struct for bestWishes since each dictionary has dissimilar data i.e.the 1st dictionary has a field birth_date & the 2nd dictionary has a field anniversary_date...?
EDIT: While making Alamofire request, this is how I'm parsing each data and assigning them to struct..
if let bestWishes = result["bestWishes"] as? [[String:Any]] {
for anItem in bestWishes {
guard let customerId = anItem["customer_id"] as? Int,
let birthdate = anItem["birth_date"] as? String,
let customerName = anItem["customer_name"] as? String,
let mobNo = anItem["mobile_number"] as? Int,
let anniversaryDate = anItem["anniversary_date"] as? String,
let type = anItem["type"] as? String
else {continue}
let bestWishes = BestWishes(customer_id: customerId, birthDate: birthdate, type: type, customer_name: customerName, mobileNo: mobNo, anniversaryDate: anniversaryDate)
self.bestWishesArr.append(bestWishes)
First of all when using Codable basically you don't need an initializer.
Without a custom initializer (related to Codable) the most suitable solution is to declare both dates as optional
struct BestWish: Codable {
private enum CodingKeys : String, CodingKey {
case customerID = "customer_id"
case birthDate = "birth_date"
case anniversaryDate = "anniversary_date"
case type
case customerName = "customer_name"
case mobileNumber = "mobile_number"
}
let customerID: Int
let birthDate: String?
let anniversaryDate: String?
let type: String
let customerName : String
let mobileNumber: Int
}
With a custom initializer declare one property date and decode anniversary or birth depending on type
struct BestWish: Decodable {
private enum CodingKeys : String, CodingKey {
case customerID = "customer_id"
case birthDate = "birth_date"
case anniversaryDate = "anniversary_date"
case type
case customerName = "customer_name"
case mobileNumber = "mobile_number"
}
let customerID: Int
let date: Date
let type: String
let customerName : String
let mobileNumber: Int
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
customerID = try container.decode(Int.self, forKey: .customerID)
type = try container.decode(String.self, forKey: .type)
if type == "anniversary_date" {
date = try container.decode(Date.self, forKey: .anniversaryDate)
} else {
date = try container.decode(Date.self, forKey: .birthDate)
}
customerName = try container.decode(String.self, forKey: .customerName)
mobileNo = try container.decode(Int.self, forKey: .mobileNumber)
}
}
You can distinguish the dates by the given type property.
To decode also the root object you need an umbrella struct
struct Root : Decodable {
// let pendingOrder : [PendingOrder]
let bestWishes : [BestWish]
}
Rather than the deserialized dictionary you have to get the JSON string in Data format from the Alamofire request to pass it to JSONDecoder
do {
let decoder = JSONDecoder()
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd"
decoder.dateDecodingStrategy = .formatted(formatter)
let result = try decoder.decode(Root.self, from: data)
print(result)
} catch { print(error) }
Edit: I added code to decode both dates as Date
Use optional type
struct BestWishes: Codable {
let birth_date: Date?
let anniversary_date: Date?
init(birth_date : Date?, anniversary_date: Date?) {
self.birth_date = birth_date
self.anniversary_date = anniversary_date
}
}