How to create a struct for this json data in swift4? - swift

I create a very simple json data for practice, but it always decode error when JSONDecoder().decode. I try some way to modify my struct, but all gets same error(prints "error0"). The code is at below.
struct ss : Codable {
var a : String
var b : String
}
let js = "[{\"a\":\"1\",\"b\":\"2\"},{\"c\":\"3\",\"d\":\"4\"}]"
let data = js.data(using: .utf8)
let a = [ss].self
do {
if let s = try? JSONDecoder().decode(a, from : data!) {
print(s[0].a)
}else{
print("error0")
}
}catch{
print("error1")
}

There is a problem with your JSON replace
let js = "[{\"a\":\"1\",\"b\":\"2\"},{\"c\":\"3\",\"d\":\"4\"}]"
with
let js = "[{\"a\":\"1\",\"b\":\"2\"},{\"a\":\"3\",\"b\":\"4\"}]"
The other dictionary don't have keys a and b and that's why JSONDecoder is not able to decode Now your update code will be:
struct ss : Codable {
var a : String
var b : String
}
let js = "[{\"a\":\"1\",\"b\":\"2\"},{\"a\":\"3\",\"b\":\"4\"}]"
let data = js.data(using: .utf8)
let a = [ss].self
do {
let jsonDecoder = JSONDecoder()
let s = try jsonDecoder.decode(a, from: data!)
print(s[0].a) //"1\n"
} catch {
print(error)
}
PS: As #Milander suggested If you don't want to fix JSON you can make optional properties in your Struct like
struct ss : Codable {
let a, b, c, d: String?
}

You can define additional keys like below:
No optional No replacement
struct ss : Codable {
var a : String
var b : String
init(from decoder: Decoder) throws {
if let con = try? decoder.container(keyedBy: CodingKeys.self), let a = try? con.decode(String.self, forKey: .a), let b = try? con.decode(String.self, forKey: .b) {
self.a = a
self.b = b
} else if let con = try? decoder.container(keyedBy: AdditionalInfoKeys.self), let c = try? con.decode(String.self, forKey: .c), let d = try? con.decode(String.self, forKey: .d) {
a = c
b = d
} else {
throw NSError(domain: "Decoding error", code: 123, userInfo: nil)
}
}
enum AdditionalInfoKeys: String, CodingKey {
case c, d
}
}

Related

Update a value on stuct codable identifiable and only userdefault cache this value

I have 2 structs codable : Student and Adress (which is linked to Student)
On my app, I fetch data from Firebase RTDB and then I store it with userdefault
Let's say a student changes his email and I just want to update the stored userdefault only with that updated email.
Do I need to specify all other data when I want to store it(name,adress,dob...) or can I just only update/store the email on my userdefault without specifying the other data ?
struct Adress : Codable, Identifiable {
var id: String { Student_UID }
var Student_UID: String
var Student_Street: String
var Student_Country: String
var Student_City: String
enum CodingKeys: String, CodingKey {
case Student_UID = "Adress_Student_UID"
case Student_Street = "Student_Street"
case Student_Country = "Student_Country"
case Student_City = "Student_City"
}
}
struct Student_Profile_Data:Codable, Identifiable {
var id: String { Student_UID }
var Student_UID: String
var Student_firstName: String?
var Student_username : String?
var Student_Email : String?
var Student_lastName: String
var Student_Address: Adress?
var Student_DOB: String?
var Student_Studentpoints : Int?
enum CodingKeys: String, CodingKey {
case Student_UID = "Student_UID"
case Student_firstName = "Student_firstName"
case Student_username = "Student_username"
case Student_Email = "Student_Email"
case Student_lastName = "Student_lastName"
case Student_Address = "Student_Address"
case Student_DOB = "Student_DOB"
case Student_Studentpoints = "Student_Studentpoints"
}
}
The USERDEfault part:
//READ
NSLog("userdefault TEST READ")
let defaults = UserDefaults.standard
if let savedStudent = defaults.object(forKey: "SavedStudent") as? Data {
let decoder = JSONDecoder()
if let loadedStudent = try? decoder.decode(Student_Profile_Data.self, from: savedStudent) {
NSLog("TEST PROFILE- username : \(loadedStudent.Student_username)")
}}
//WRITE
let add1 = Adress(Student_UID: "", Student_Street: "", Student_Country: "", Student_City: "")
let stud = Student_Profile_Data(Student_firstName: "", Student_username: "Martin", Student_Email: "", Student_lastName: "", Student_Address: add1, Student_DOB: "", Student_Studentpoints: 0, Student_UID: "")
let encoder = JSONEncoder()
if let encoded = try? encoder.encode(stud) {
let defaults = UserDefaults.standard
defaults.set(encoded, forKey: "SavedStudent")
NSLog("WRITE OK »)
}
//UPDATE ONE (OR TWO) VALUES ?
If you are storing the student in UserDefault in this way, then updating it would involve the three-step process of reading the student, updating its value, then writing it back.
// read
let defaults = UserDefaults.standard
let decoder = JSONDecoder()
var savedStudent = defaults.data(forKey: "SavedStudent").flatMap {
try? decoder.decode(StudentProfileData.self, from: $0)
} ?? StudentProfileData(...) // some "empty" student to use when there is no previously saved student
// update
savedStudent.firstName = "John"
// write
let encoder = JSONEncoder()
if let encoded = try? encoder.encode(savedStudent) {
defaults.set(encoded, forKey: "SavedStudent")
}
To make this more convenient, you can extract this as a function:
func updateSavedStudent(updateBlock: (inout StudentProfileData) -> Void) {
// read
let defaults = UserDefaults.standard
let decoder = JSONDecoder()
var savedStudent = defaults.data(forKey: "SavedStudent").flatMap {
try? decoder.decode(StudentProfileData.self, from: $0)
} ?? StudentProfileData(...) // some "empty" student to use when there is no previously saved student
// update
updateBlock(&savedStudent)
// write
let encoder = JSONEncoder()
if let encoded = try? encoder.encode(savedStudent) {
defaults.set(encoded, forKey: "SavedStudent")
}
}
// usage:
updateSavedStudent {
$0.firstName = "John"
$0.lastName = "Smith"
}
Alternatively, make a computed property for this saved student and put it in a utility class somewhere. Do note that this will encode and decode the student once for every property you update though.
static var savedStudent: StudentProfileData {
get {
let defaults = UserDefaults.standard
let decoder = JSONDecoder()
return defaults.data(forKey: "SavedStudent").flatMap {
try? decoder.decode(StudentProfileData.self, from: $0)
} ?? StudentProfileData(...) // some "empty" student to use when there is no previously saved student
}
set {
let encoder = JSONEncoder()
if let encoded = try? encoder.encode(newValue) {
defaults.set(encoded, forKey: "SavedStudent")
}
}
}
// usage
savedStudent.firstName = "John"

Prevent lost of data on AppStorage when changing a struct

I have a data model that handles a structure and the data the app uses. I'm saving that data using AppStorage.
I recently needed to add an extra value to the struct, and when I did that, all the data saved was gone.
is there any way to prevent this? I can't find anything on Apple's documentation, or other Swift or SwiftUI sites about this.
Here's my data structure and how I save it.
let dateFormatter = DateFormatter()
struct NoteItem: Codable, Hashable, Identifiable {
let id: UUID
var text: String
var date = Date()
var dateText: String {
dateFormatter.dateFormat = "EEEE, MMM d yyyy, h:mm a"
return dateFormatter.string(from: date)
}
var tags: [String] = []
//var starred: Int = 0 // if I add this, it wipes all the data the app has saved
}
final class DataModel: ObservableObject {
#AppStorage("myappdata") public var notes: [NoteItem] = []
init() {
self.notes = self.notes.sorted(by: {
$0.date.compare($1.date) == .orderedDescending
})
}
func sortList() {
self.notes = self.notes.sorted(by: {
$0.date.compare($1.date) == .orderedDescending
})
}
}
extension Array: RawRepresentable where Element: Codable {
public init?(rawValue: String) {
guard let data = rawValue.data(using: .utf8),
let result = try? JSONDecoder().decode([Element].self, from: data)
else {
return nil
}
self = result
}
public var rawValue: String {
guard let data = try? JSONEncoder().encode(self),
let result = String(data: data, encoding: .utf8)
else {
return "[]"
}
return result
}
}
I certainly agree that UserDefaults (AppStorage) is no the best choice for this but whatever storage solution you choose you are going to need a migration strategy. So here are two routes you can take to migrate a changed json struct.
The first one is to add a custom init(from:) to your struct and handle the new property separately
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
id = try container.decode(UUID.self, forKey: .id)
text = try container.decode(String.self, forKey: .text)
date = try container.decode(Date.self, forKey: .date)
tags = try container.decode([String].self, forKey: .tags)
if let value = try? container.decode(Int.self, forKey: .starred) {
starred = value
} else {
starred = 0
}
}
The other option is to keep the old version of the struct with another name and use it if the decoding fails for the ordinary struct and then convert the result to the new struct
extension NoteItem {
static func decode(string: String) -> [NoteItem]? {
guard let data = string.data(using: .utf8) else { return nil }
if let result = try? JSONDecoder().decode([NoteItem].self, from: data) {
return result
} else if let result = try? JSONDecoder().decode([NoteItemOld].self, from: data) {
return result.map { NoteItem(id: $0.id, text: $0.text, date: $0.date, tags: $0.tags, starred: 0)}
}
return nil
}
}

Swift : Possible to overload randomVar as? [MyStruct]?

My server returns an array of photo informations in JSON like that :
"pics":[{"ID":182,"ID_member":39,"fn":"b69ea6f6c88b58c67a331aa3c5eaff81.jpg"}, ...]
I have a struct init function made to handle one photo json raw array (from type [String:Any]) :
init?(fromRaw _img:[String:Any]?)
{
guard
let img = _img,
let id = img["ID"] as? Int,
let idm = img["ID_member"] as? Int,
let fn = img["fn"] as? String
else
{
OOTLog.info("Warning : unable to init from photo raw array")
return nil ;
}
self.id = id
self.idMembre = idm
self.fileName = fn
}
My question is : lets say we have a json from server (of type [[String:Any]], array of n raw photos), is there any way to "overload" as? [Photo] with my init?(fromRaw:) within Photo struct, so we could just code :
guard let arrayPhoto = jsonRaw as? [Photo] else ..
Instead of :
guard let arrayPhotoRaw = jsonRaw as [[String:Any]] else ..
let photoArray:[Photo] = []
for p in jsonRaw {
guard let p = Photo(fromRaw:p) else { continue }
photoArray.append(p)
}
It's better to use
struct Root: Codable {
let pics: [Pic]
}
struct Pic: Codable {
let id, idMember: Int
let fn: String
enum CodingKeys: String, CodingKey {
case id = "ID"
case idMember = "ID_member"
case fn
}
}
let res = try! JSONDecoder().decode(Root.self, from:data)
print(res.pics)

Field level custom decoder

I am trying to implement field level custom decoding, so that a decoder function can be supplied to map a value. This was originally intended to solve automagically turning string values of "Y" and "N" into true / false. Is there a less verbose way I can do this?
This was intended to be used for a single field of a fairly decent sized record... but has gotten somewhat out of hand.
The main objective was not to have to manually implement decoding of every single
field in the record, but to enumerate through them and use the result of the default decoder for anything that did not have a custom decoder (which probably should not be called a "decoder").
Current attempt shown below:
class Foo: Decodable {
var bar: String
var baz: String
init(foo: String) {
self.bar = foo
self.baz = ""
}
enum CodingKeys: String, CodingKey {
case bar
case baz
}
static func customDecoder(for key: CodingKey) -> ((String) -> Any)? {
switch key {
case CodingKeys.baz: return { return $0 == "meow" ? "foo" : "bar" }
default:
return nil
}
}
required init(from decoder: Decoder) throws {
let values: KeyedDecodingContainer = try decoder.container(keyedBy: CodingKeys.self)
if let cde = Foo.customDecoder(for: CodingKeys.bar) {
self.bar = (try cde(values.decode(String.self, forKey: .bar)) as? String)!
} else {
self.bar = try values.decode(type(of: self.bar), forKey: .bar)
}
if let cde = Foo.customDecoder(for: CodingKeys.baz) {
self.baz = (try cde(values.decode(String.self, forKey: .baz)) as? String)!
} else {
self.baz = try values.decode(type(of: self.baz), forKey: .baz)
}
}
}
Example of use:
func testFoo() {
var foo: Foo?
let jsonData = """
{"bar": "foo", "baz": "meow"}
""".data(using: .utf8)
if let data = jsonData {
foo = try? JSONDecoder().decode(Foo.self, from: data)
if let bar = foo {
XCTAssertEqual(bar.bar, "foo")
} else {
XCTFail("bar is not foo")
}
} else {
XCTFail("Could not coerce string into JSON")
}
}
For example we have an example json:
let json = """
{
"id": 1,
"title": "Title",
"thumbnail": "https://www.sample-videos.com/img/Sample-jpg-image-500kb.jpg",
"date": "2014-07-15"
}
""".data(using: .utf8)!
If we want parse that, we can use Codable protocol and simple NewsCodable struct:
public struct NewsCodable: Codable {
public let id: Int
public let title: String
public let thumbnail: PercentEncodedUrl
public let date: MyDate
}
PercentEncodedUrl is our custom Codable wrapper for URL, which adding percent encoding to url string. Standard URL does not support that out of the box.
public struct PercentEncodedUrl: Codable {
public let url: URL
public init(from decoder: Decoder) throws {
let urlString = try decoder.singleValueContainer().decode(String.self)
guard
let encodedUrlString = urlString.addingPercentEncoding(withAllowedCharacters: .urlFragmentAllowed),
let url = URL.init(string: encodedUrlString) else {
throw PercentEncodedUrlError.url(urlString)
}
self.url = url
}
public enum PercentEncodedUrlError: Error {
case url(String)
}
}
If some strange reasons we need custom decoder for date string(Date decoding has plenty of support in JSONDecoder), we can provide wrapper like PercentEncodedUrl.
public struct MyDate: Codable {
public let date: Date
public init(from decoder: Decoder) throws {
let dateString = try decoder.singleValueContainer().decode(String.self)
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd"
guard let date = dateFormatter.date(from: dateString) else {
throw MyDateError.date(dateString)
}
self.date = date
}
public enum MyDateError: Error {
case date(String)
}
}
let decoder = JSONDecoder()
let news = try! decoder.decode(NewsCodable.self, from: json)
So we provided field level custom decoder.

How can I use Swift’s Codable to encode into a dictionary?

I have a struct that implements Swift 4’s Codable. Is there a simple built-in way to encode that struct into a dictionary?
let struct = Foo(a: 1, b: 2)
let dict = something(struct)
// now dict is ["a": 1, "b": 2]
If you don't mind a bit of shifting of data around you could use something like this:
extension Encodable {
func asDictionary() throws -> [String: Any] {
let data = try JSONEncoder().encode(self)
guard let dictionary = try JSONSerialization.jsonObject(with: data, options: .allowFragments) as? [String: Any] else {
throw NSError()
}
return dictionary
}
}
Or an optional variant
extension Encodable {
var dictionary: [String: Any]? {
guard let data = try? JSONEncoder().encode(self) else { return nil }
return (try? JSONSerialization.jsonObject(with: data, options: .allowFragments)).flatMap { $0 as? [String: Any] }
}
}
Assuming Foo conforms to Codable or really Encodable then you can do this.
let struct = Foo(a: 1, b: 2)
let dict = try struct.asDictionary()
let optionalDict = struct.dictionary
If you want to go the other way(init(any)), take a look at this Init an object conforming to Codable with a dictionary/array
Here are simple implementations of DictionaryEncoder / DictionaryDecoder that wrap JSONEncoder, JSONDecoder and JSONSerialization, that also handle encoding / decoding strategies…
class DictionaryEncoder {
private let encoder = JSONEncoder()
var dateEncodingStrategy: JSONEncoder.DateEncodingStrategy {
set { encoder.dateEncodingStrategy = newValue }
get { return encoder.dateEncodingStrategy }
}
var dataEncodingStrategy: JSONEncoder.DataEncodingStrategy {
set { encoder.dataEncodingStrategy = newValue }
get { return encoder.dataEncodingStrategy }
}
var nonConformingFloatEncodingStrategy: JSONEncoder.NonConformingFloatEncodingStrategy {
set { encoder.nonConformingFloatEncodingStrategy = newValue }
get { return encoder.nonConformingFloatEncodingStrategy }
}
var keyEncodingStrategy: JSONEncoder.KeyEncodingStrategy {
set { encoder.keyEncodingStrategy = newValue }
get { return encoder.keyEncodingStrategy }
}
func encode<T>(_ value: T) throws -> [String: Any] where T : Encodable {
let data = try encoder.encode(value)
return try JSONSerialization.jsonObject(with: data, options: .allowFragments) as! [String: Any]
}
}
class DictionaryDecoder {
private let decoder = JSONDecoder()
var dateDecodingStrategy: JSONDecoder.DateDecodingStrategy {
set { decoder.dateDecodingStrategy = newValue }
get { return decoder.dateDecodingStrategy }
}
var dataDecodingStrategy: JSONDecoder.DataDecodingStrategy {
set { decoder.dataDecodingStrategy = newValue }
get { return decoder.dataDecodingStrategy }
}
var nonConformingFloatDecodingStrategy: JSONDecoder.NonConformingFloatDecodingStrategy {
set { decoder.nonConformingFloatDecodingStrategy = newValue }
get { return decoder.nonConformingFloatDecodingStrategy }
}
var keyDecodingStrategy: JSONDecoder.KeyDecodingStrategy {
set { decoder.keyDecodingStrategy = newValue }
get { return decoder.keyDecodingStrategy }
}
func decode<T>(_ type: T.Type, from dictionary: [String: Any]) throws -> T where T : Decodable {
let data = try JSONSerialization.data(withJSONObject: dictionary, options: [])
return try decoder.decode(type, from: data)
}
}
Usage is similar to JSONEncoder / JSONDecoder…
let dictionary = try DictionaryEncoder().encode(object)
and
let object = try DictionaryDecoder().decode(Object.self, from: dictionary)
For convenience, I've put this all in a repo… https://github.com/ashleymills/SwiftDictionaryCoding
I have create a library called CodableFirebase and it's initial purpose was to use it with Firebase Database, but it does actually what you need: it creates a dictionary or any other type just like in JSONDecoder but you don't need to do the double conversion here like you do in other answers. So it would look something like:
import CodableFirebase
let model = Foo(a: 1, b: 2)
let dict = try! FirebaseEncoder().encode(model)
There is no built in way to do that.
As answered above if you have no performance issues then you can accept the JSONEncoder + JSONSerialization implementation.
But I would rather go the standard library's way to provide an encoder/decoder object.
class DictionaryEncoder {
private let jsonEncoder = JSONEncoder()
/// Encodes given Encodable value into an array or dictionary
func encode<T>(_ value: T) throws -> Any where T: Encodable {
let jsonData = try jsonEncoder.encode(value)
return try JSONSerialization.jsonObject(with: jsonData, options: .allowFragments)
}
}
class DictionaryDecoder {
private let jsonDecoder = JSONDecoder()
/// Decodes given Decodable type from given array or dictionary
func decode<T>(_ type: T.Type, from json: Any) throws -> T where T: Decodable {
let jsonData = try JSONSerialization.data(withJSONObject: json, options: [])
return try jsonDecoder.decode(type, from: jsonData)
}
}
You can try it with following code:
struct Computer: Codable {
var owner: String?
var cpuCores: Int
var ram: Double
}
let computer = Computer(owner: "5keeve", cpuCores: 8, ram: 4)
let dictionary = try! DictionaryEncoder().encode(computer)
let decodedComputer = try! DictionaryDecoder().decode(Computer.self, from: dictionary)
I am force-trying here to make the example shorter. In production code you should handle the errors appropriately.
I'm not sure if it's the best way but you definitely can do something like:
struct Foo: Codable {
var a: Int
var b: Int
init(a: Int, b: Int) {
self.a = a
self.b = b
}
}
let foo = Foo(a: 1, b: 2)
let dict = try JSONDecoder().decode([String: Int].self, from: JSONEncoder().encode(foo))
print(dict)
let dict = try JSONSerialization.jsonObject(with: try JSONEncoder().encode(struct), options: []) as? [String: Any]
I have modified the PropertyListEncoder from the Swift project into a DictionaryEncoder, simply by removing the final serialisation from dictionary into binary format. You can do the same yourself, or you can take my code from here
It can be used like this:
do {
let employeeDictionary: [String: Any] = try DictionaryEncoder().encode(employee)
} catch let error {
// handle error
}
In some project, i'm used the swift reflection. But be careful, nested codable objects, are not mapped also there.
let dict = Dictionary(uniqueKeysWithValues: Mirror(reflecting: foo).children.map{ ($0.label!, $0.value) })
I definitely think that there's some value in just being able to use Codable to encode to/from dictionaries, without the intention of ever hitting JSON/Plists/whatever. There are plenty of APIs which just give you back a dictionary, or expect a dictionary, and it's nice to be able to interchange them easily with Swift structs or objects, without having to write endless boilerplate code.
I've been playing round with some code based on the Foundation JSONEncoder.swift source (which actually does implement dictionary encoding/decoding internally, but doesn't export it).
The code can be found here: https://github.com/elegantchaos/DictionaryCoding
It's still quite rough, but I've expanded it a bit so that, for example, it can fill in missing values with defaults when decoding.
Here is a protocol based solution:
protocol DictionaryEncodable {
func encode() throws -> Any
}
extension DictionaryEncodable where Self: Encodable {
func encode() throws -> Any {
let jsonData = try JSONEncoder().encode(self)
return try JSONSerialization.jsonObject(with: jsonData, options: .allowFragments)
}
}
protocol DictionaryDecodable {
static func decode(_ dictionary: Any) throws -> Self
}
extension DictionaryDecodable where Self: Decodable {
static func decode(_ dictionary: Any) throws -> Self {
let jsonData = try JSONSerialization.data(withJSONObject: dictionary, options: [])
return try JSONDecoder().decode(Self.self, from: jsonData)
}
}
typealias DictionaryCodable = DictionaryEncodable & DictionaryDecodable
And here is how to use it:
class AClass: Codable, DictionaryCodable {
var name: String
var age: Int
init(name: String, age: Int) {
self.name = name
self.age = age
}
}
struct AStruct: Codable, DictionaryEncodable, DictionaryDecodable {
var name: String
var age: Int
}
let aClass = AClass(name: "Max", age: 24)
if let dict = try? aClass.encode(), let theClass = try? AClass.decode(dict) {
print("Encoded dictionary: \n\(dict)\n\ndata from decoded dictionary: \"name: \(theClass.name), age: \(theClass.age)\"")
}
let aStruct = AStruct(name: "George", age: 30)
if let dict = try? aStruct.encode(), let theStruct = try? AStruct.decode(dict) {
print("Encoded dictionary: \n\(dict)\n\ndata from decoded dictionary: \"name: \(theStruct.name), age: \(theStruct.age)\"")
}
I wrote a quick gist to handle this (not using the Codable protocol). Be careful, it doesn't type-check any values and doesn't work recursively on values that are encodable.
class DictionaryEncoder {
var result: [String: Any]
init() {
result = [:]
}
func encode(_ encodable: DictionaryEncodable) -> [String: Any] {
encodable.encode(self)
return result
}
func encode<T, K>(_ value: T, key: K) where K: RawRepresentable, K.RawValue == String {
result[key.rawValue] = value
}
}
protocol DictionaryEncodable {
func encode(_ encoder: DictionaryEncoder)
}
There no straight forward way of doing this in Codable. You need to implement Encodable/Decodable protocol for your struct. For your example, you might need to write as below
typealias EventDict = [String:Int]
struct Favorite {
var all:EventDict
init(all: EventDict = [:]) {
self.all = all
}
}
extension Favorite: Encodable {
struct FavoriteKey: CodingKey {
var stringValue: String
init?(stringValue: String) {
self.stringValue = stringValue
}
var intValue: Int? { return nil }
init?(intValue: Int) { return nil }
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: FavoriteKey.self)
for eventId in all {
let nameKey = FavoriteKey(stringValue: eventId.key)!
try container.encode(eventId.value, forKey: nameKey)
}
}
}
extension Favorite: Decodable {
public init(from decoder: Decoder) throws {
var events = EventDict()
let container = try decoder.container(keyedBy: FavoriteKey.self)
for key in container.allKeys {
let fav = try container.decode(Int.self, forKey: key)
events[key.stringValue] = fav
}
self.init(all: events)
}
}
I have made a pod here https://github.com/levantAJ/AnyCodable to facilitate decode and encode [String: Any] and [Any]
pod 'DynamicCodable', '1.0'
And you are able to decode & encode [String: Any] and [Any]
import DynamicCodable
struct YourObject: Codable {
var dict: [String: Any]
var array: [Any]
var optionalDict: [String: Any]?
var optionalArray: [Any]?
enum CodingKeys: String, CodingKey {
case dict
case array
case optionalDict
case optionalArray
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
dict = try values.decode([String: Any].self, forKey: .dict)
array = try values.decode([Any].self, forKey: .array)
optionalDict = try values.decodeIfPresent([String: Any].self, forKey: .optionalDict)
optionalArray = try values.decodeIfPresent([Any].self, forKey: .optionalArray)
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(dict, forKey: .dict)
try container.encode(array, forKey: .array)
try container.encodeIfPresent(optionalDict, forKey: .optionalDict)
try container.encodeIfPresent(optionalArray, forKey: .optionalArray)
}
}
After research, we find that if we use the keyword Any in the class which is inherited from the Codable & Decodable it will give the error. So if you want to use a dictionary user with the types of data coming from the server.
For example, the server is sending the dictionary of type [String : Int] then use [String : Int] if you will try [String : Any] it will not work.
Here is dictionary -> object. Swift 5.
extension Dictionary where Key == String, Value: Any {
func object<T: Decodable>() -> T? {
if let data = try? JSONSerialization.data(withJSONObject: self, options: []) {
return try? JSONDecoder().decode(T.self, from: data)
} else {
return nil
}
}
}
Come to think of it, the question does not have an answer in the general case, since the Encodable instance may be something not serializable into a dictionary, such as an array:
let payload = [1, 2, 3]
let encoded = try JSONEncoder().encode(payload) // "[1,2,3]"
Other than that, I have written something similar as a framework.