I have N classes all deriving from Base :
class Base {...}
class A : Base {
init(a:Int) {...}
}
class B : Base {
init(b:Int, c:Int) {...}
}
They have been serialized into a json file that contains an array of Base. It looks like this:
{
"elements" : [
{
"type" : "A",
"a" : 3
},
{
"type" : "B",
"b" : 30,
"c" : 45
}
]
}
When I decode the json, I have a dictionary like one of the 2 above. How can I then create an object of one of my N classes from it ?
Important note : for various reasons, I cannot use Codable, and JSONEncoder, JSONDecoder
As the JSON contains the type use it to determine the different classes
if let elements = root["elements"] as? [[String:Any]] {
for element in elements {
let type = element["type"] as! String
switch type {
case "A":
let a = element["a"] as! Int
let aInstance = A(a: a)
case "B": // decode b and c and create instance of B
// and so on
}
}
}
For classes, it's not quite as straightforward as for structs, but it's still pretty easy to implement this kind of Decodable.
struct ElementContainer: Decodable {
let elements: [Base]
enum CodingKeys: String, CodingKey { case elements }
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.elements = try container.decode([Element].self, forKey: .elements)
.map { $0.base }
}
}
struct Element: Decodable {
let base: Base
enum CodingKeys: String, CodingKey {
case type
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let type = try container.decode(String.self, forKey: .type)
switch type {
case "A":
base = try A(from: decoder)
case "B":
base = try B(from: decoder)
default:
throw DecodingError.dataCorruptedError(forKey: .type,
in: container,
debugDescription: "Unknown type: \(type)")
}
}
}
class Base {}
class A : Base, Decodable {
let a: Int
init(a:Int) {
self.a = a
super.init()
}
}
class B : Base, Decodable {
let b: Int
let c: Int
init(b:Int, c:Int) {
self.b = b
self.c = c
super.init()
}
}
let results = try JSONDecoder().decode(ElementContainer.self, from: json).elements
Structs are a little simpler and you can get rid of the switch statement. It's harder to do this with classes because it introduces required inits that are tedious to implement.
struct ElementContainer: Decodable {
let elements: [Element]
}
struct Element: Decodable {
let base: Base
enum CodingKeys: String, CodingKey {
case type
}
static let mapping: [String: Base.Type] = ["A": A.self, "B": B.self]
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let type = try container.decode(String.self, forKey: .type)
guard let elementType = Element.mapping[type] else {
throw DecodingError.dataCorruptedError(forKey: .type,
in: container,
debugDescription: "Unknown type: \(type)")
}
base = try elementType.init(from: decoder)
}
}
protocol Base: Decodable {}
struct A : Base {
let a: Int
}
struct B : Base {
let b: Int
let c: Int
}
Related
I have a the following structure:
JSON a:
{
"type": "A",
"data": {
"aSpecific": 64
}
}
or JSON b:
{
"type": "B",
"data": {
"bSpecific": "hello"
}
}
Now how does the structure look like to parse any of the above in one go?
enum DataType {
case "A"
case "B"
}
struct Example: Codable {
struct ASpecific: Codable {
var aSpecifiv: Int
}
struct BSpecific: Codable {
var bSpecifiv: String
}
var type: DataType
var data: ??? // Aspecific or BSpecific
}
I want the var data to be specific for the type of the JSON.
How do I do this?
First of all, use String as rawType of enum DataType
enum DataType: String, Decodable {
case A, B
}
Now, you can create init(from:) in the struct Root and add custom parsing as per the JSON.
struct Root: Decodable {
let type: DataType
let aSpecific: Int?
let bSpecific: String?
enum CodingKeys: String, CodingKey {
case type, data, aSpecific, bSpecific
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
type = try container.decode(DataType.self, forKey: .type)
let data = try container.nestedContainer(keyedBy: CodingKeys.self, forKey: .data)
aSpecific = try data.decodeIfPresent(Int.self, forKey: .aSpecific)
bSpecific = try data.decodeIfPresent(String.self, forKey: .bSpecific)
}
}
Parsing:
do {
let response = try JSONDecoder().decode(Root.self, from: data)
print(response)
} catch {
print(error)
}
For two different types and one common key my suggestion is an enum with associated values. It's easy to distinguish on the basis of the type and you don't need any optionals.
enum DataType : String, Decodable {
case A, B
}
struct ASpecific: Decodable {
var aSpecific: Int
}
struct BSpecific: Decodable {
var bSpecific: String
}
enum Response : Decodable {
case aType(ASpecific)
case bType(BSpecific)
private enum CodingKeys : String, CodingKey {
case type, data
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let type = try container.decode(DataType.self, forKey: .type)
switch type {
case .A: self = .aType(try container.decode(ASpecific.self, forKey: .data))
case .B: self = .bType(try container.decode(BSpecific.self, forKey: .data))
}
}
}
This question already has answers here:
Codable enum with default case in Swift 4
(10 answers)
Closed 3 years ago.
For a given JSON like below:
{
"store": {
"animals": [
{
"type": "dog"
},
{
"type": "cat"
}
]
}
}
I can parse it with enum for type like following:
final class AnimalStore: Decodable {
let store: Store
}
extension AnimalStore {
struct Store: Decodable {
let animals: [Animal]
}
}
extension AnimalStore.Store {
struct Animal: Decodable {
let type: AnimalType?
}
}
extension AnimalStore.Store.Animal {
enum AnimalType: String, Decodable {
case dog = "dog"
case cat = "cat"
//case unknown = how such a case could be implemented?
}
}
And also since it is optional; it would work fine if type key value pair would be missing from json.
But I would like to have another case, lets call it unknown so that if any given type is not dog or cat (string being something else),type would be initialised as unknown. Right now it crashes if a type, other than dog or cat is given.
How initialising with another type other than the ones given can be implemented with enum?
In other words, for a given type like: "type": "bird" I would like type to be initialised as unknown.
Add the enum case with a string, you may as well use "unknown".
To convert non-matching strings to unknowns, you have to manually implement init(from decoder: Decoder) at some point, either in your Animal or in AnimalType. I'd favour using AnimalType so that you don't have to manually decode any of the other properties of Animal.
enum AnimalType: String, Decodable {
case dog = "dog"
case cat = "cat"
case unknown = "unknown"
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
let string = try container.decode(String.self)
self = AnimalType(rawValue: string) ?? .unknown
}
}
If you did it in Animal, you'd need something like:
// Decode everything else...
type = try? decoder.decode(AnimalType.self, forKey: .type) ?? .unknown
If you want to allow some alternative to your enum values you may use something like this:
enum Alt<S, A> {
case standard(S)
case alternative(A)
}
extension Alt: Decodable where S: Decodable, A: Decodable {
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if let standard = try? container.decode(S.self) {
self = .standard(standard)
} else if let alternative = try? container.decode(A.self) {
self = .alternative(alternative)
} else {
throw DecodingError.typeMismatch(
Self.self,
DecodingError.Context(codingPath: container.codingPath, debugDescription: "")
)
}
}
}
And then change AnimalStore.Store.Animal declaration to this:
extension AnimalStore.Store {
struct Animal: Decodable {
let type: Alt<AnimalType, String>?
}
}
Now it will try to decode it as AnimalType first and then, if fails will decode it as alternative type. So you can keep the value of the strings not in your enum.
EDIT: Or in situations when alternative is RawValue of standard you may use something like this:
enum RawBox<T>: RawRepresentable where T: RawRepresentable {
typealias RawValue = T.RawValue
case packed(T)
case raw(RawValue)
init(rawValue: Self.RawValue) {
if let packed = T(rawValue: rawValue) {
self = .packed(packed)
} else {
self = .raw(rawValue)
}
}
var rawValue: T.RawValue {
switch self {
case .packed(let packed):
return packed.rawValue
case .raw(let raw):
return raw
}
}
}
extension RawBox: Decodable where RawValue: Decodable {
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
let raw = try container.decode(RawValue.self)
self.init(rawValue: raw)
}
}
extension RawBox: Encodable where RawValue: Encodable {
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(self.rawValue)
}
}
…
extension AnimalStore.Store {
struct Animal: Decodable {
let type: RawBox<AnimalType>?
}
}
I think you can try with this
extension AnimalStore.Store {
struct Animal: Decodable {
let type: AnimalType?
enum CodingKeys: String, CodingKey {
case type
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
type = try? values.decode(AnimalType.self, forKey: .type) ?? .unknown
}
}
}
extension AnimalStore.Store.Animal {
enum AnimalType: String {
case dog
case cat
case unknown
}
}
I have a problem decoding a JSON structure which I cannot change to make it easier to decode (it's coming from firebase)..
How do I decode the following JSON into objects?
The problem is how to convert "7E7-M001". It's the name of a container which has drawers. The drawers name is also used as a key.
{
"7E7-M001" : {
"Drawer1" : {
"101" : {
"Partnumber" : "F101"
},
"102" : {
"Partnumber" : "F121"
}
}
},
"7E7-M002": {
"Drawer1": {
"201": {
"Partnumber": "F201"
},
"202": {
"Partnumber": "F221"
}
}
}
}
What do I have to fix in the Container & Drawer class to have the key as a title property and an array of objects in these classes ?
class Container: Codable {
var title: String
var drawers: [Drawer]
}
class Drawer: Codable {
var title: String
var tools: [Tool]
}
class Tool: Codable {
var title: String
var partNumber: String
enum CodingKeys: String, CodingKey {
case partNumber = "Partnumber"
}
}
First I'm going to make some slight simplifications so I can focus on the important points of this question. I'm going to make everything immutable, replace the classes with structs, and only implement Decodable. Making this Encodable is a separate issue.
The central tool for handling unknown value keys is a CodingKey that can handle any string:
struct TitleKey: CodingKey {
let stringValue: String
init?(stringValue: String) { self.stringValue = stringValue }
var intValue: Int? { return nil }
init?(intValue: Int) { return nil }
}
The second important tool is the ability to know your own title. That means asking the decoder "where are we?" That's the last element in the current coding path.
extension Decoder {
func currentTitle() throws -> String {
guard let titleKey = codingPath.last as? TitleKey else {
throw DecodingError.dataCorrupted(.init(codingPath: codingPath,
debugDescription: "Not in titled container"))
}
return titleKey.stringValue
}
}
And then we need a way to decode elements that are "titled" this way:
extension Decoder {
func decodeTitledElements<Element: Decodable>(_ type: Element.Type) throws -> [Element] {
let titles = try container(keyedBy: TitleKey.self)
return try titles.allKeys.map { title in
return try titles.decode(Element.self, forKey: title)
}
}
}
With that, we can invent a protocol for these "titled" things and decode them:
protocol TitleDecodable: Decodable {
associatedtype Element: Decodable
init(title: String, elements: [Element])
}
extension TitleDecodable {
init(from decoder: Decoder) throws {
self.init(title: try decoder.currentTitle(),
elements: try decoder.decodeTitledElements(Element.self))
}
}
And that's most of the work. We can use this protocol to make decoding pretty easy for the upper-level layers. Just implement init(title:elements:).
struct Drawer: TitleDecodable {
let title: String
let tools: [Tool]
init(title: String, elements: [Tool]) {
self.title = title
self.tools = elements
}
}
struct Container: TitleDecodable {
let title: String
let drawers: [Drawer]
init(title: String, elements: [Drawer]) {
self.title = title
self.drawers = elements
}
}
Tool is a little different since it's a leaf node and has other things to decode.
struct Tool: Decodable {
let title: String
let partNumber: String
enum CodingKeys: String, CodingKey {
case partNumber = "Partnumber"
}
init(from decoder: Decoder) throws {
self.title = try decoder.currentTitle()
let container = try decoder.container(keyedBy: CodingKeys.self)
self.partNumber = try container.decode(String.self, forKey: .partNumber)
}
}
That just leaves the very top level. We'll create a Containers type just to wrap things up.
struct Containers: Decodable {
let containers: [Container]
init(from decoder: Decoder) throws {
self.containers = try decoder.decodeTitledElements(Container.self)
}
}
And to use it, decode the top level Containers:
let containers = try JSONDecoder().decode(Containers.self, from: json)
print(containers.containers)
Note that since JSON objects are not order-preserving, the arrays may not be in the same order as the JSON, and may not be in the same order between runs.
Gist
I'm going to extend Rob's answer to give a more general answer and to give it more capabilities. First we'll take an example Json and identify all the scenarios that can be contained within.
let json = Data("""
{
"id": "123456", // id -> primitive data type that can be decoded normally
"name": "Example Name", // name -> primitive data type that can be decoded
"address": { // address -> key => static, object => has static key-value pairs
"city": "Negombo",
"country": "Sri Lanka"
},
"email": { // email -> key => static, object => has only one key-value pair which has a dynamic key. When you're sure, user can have only one email.
"example#gmail.com": { // example#gmail.com -> key => dynamic key, object => in this example the object is
// normal decodable object. But you can have objects that has dynamic key-value pairs.
"verified": true
}
},
"phone_numbers": { // phone_numbers -> key => static, object => has multiple key-value pairs which has a dynamic keys. Assume user can have multiple phone numbers.
"+94772222222": { // +94772222222 -> key => dynamic key, object => in this example the object is
// normal decodable object. But you can have objects that has dynamic key-value pairs.
"isActive": true
},
"+94772222223": { // +94772222223 -> key => another dynamic key, object => another object mapped to dynamic key +94772222223
"isActive": false
}
}
}
""".utf8)
At the end you will be able to read all the values as follows,
let decoder = JSONDecoder()
do {
let userObject = try decoder.decode(UserModel.self, from: json)
print("User ID : \(String(describing: userObject.id))")
print("User Name : \(String(describing: userObject.name))")
print("User Address city : \(String(describing: userObject.address?.city))")
print("User Address country: \(String(describing: userObject.address?.country))")
print("User Email. : \(String(describing: userObject.email?.emailContent?.emailAddress))")
print("User Email Verified : \(String(describing: userObject.email?.emailContent?.verified))")
print("User Phone Number 1 : \(String(describing: userObject.phoneNumberDetails?.phoneNumbers.first?.number))")
print("User Phone Number 2 : \(String(describing: userObject.phoneNumberDetails?.phoneNumbers[1].number))")
print("User Phone Number 1 is Active : \(String(describing: userObject.phoneNumberDetails?.phoneNumbers.first?.isActive))")
print("User Phone Number 2 is Active : \(String(describing: userObject.phoneNumberDetails?.phoneNumbers[1].isActive))")
} catch {
print("Error deserializing JSON: \(error)")
}
So up to address key, you can easily Decode. But after that you're gonna need a specific Object structure to hold all the data mapped by dynamic key-value pairs.
So here is my suggested Swift Object structure. Assume the above Json is for UserModel.
import Foundation
struct UserModel: Decodable {
let id: String
let name: String
let address: Address?
let email: Email?
let phoneNumberDetails: PhoneNumberDetails?
enum CodingKeys: String, CodingKey {
case id
case name
case address
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.id = try container.decode(String.self, forKey: .id)
self.name = try container.decode(String.self, forKey: .name)
self.address = try? container.decode(Address.self, forKey: .address)
// ["email": Value] -> static key => Email Swift Object
// ["email": Value] -> only object => email.emailContent. Here Value has only one object.
self.email = try decoder.decodeStaticTitledElement(with: TitleKey(stringValue: "email")!, Email.self)
// ["phone_numbers": Value] -> static key => PhoneNumberDetails Swift Object
// ["phone_numbers": Value] -> multiple objects => phoneNumberDetails.phoneNumbers. Here Value has multiples objects.
self.phoneNumberDetails = try decoder.decodeStaticTitledElement(with: TitleKey(stringValue: "phone_numbers")!, PhoneNumberDetails.self)
}
}
struct Address: Decodable {
let city: String
let country: String
enum CodingKeys: String, CodingKey {
case city
case country
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.city = try container.decode(String.self, forKey: .city)
self.country = try container.decode(String.self, forKey: .country)
}
}
/*
* Extends SingleTitleDecodable.
* Object that was mapped to static key "email".
* SingleTitleDecodable uses when you know the Parent object has only one dynamic key-value pair
* In this case Parent object is "email" object in the json, and "example#gmail.com": { body } is the only dynamic key-value pair
* key-value pair is mapped into EmailContent
*/
struct Email: SingleTitleDecodable {
let emailContent: EmailContent?
init(title: String, element: EmailContent?) {
self.emailContent = element
}
}
struct EmailContent: Decodable {
let emailAddress: String
let verified: Bool
enum CodingKeys: String, CodingKey {
case verified
}
init(from decoder: Decoder) throws {
self.emailAddress = try decoder.currentTitle()
let container = try decoder.container(keyedBy: CodingKeys.self)
self.verified = try container.decode(Bool.self, forKey: .verified)
}
}
/*
* Extends TitleDecodable.
* Object that was mapped to static key "phone_numbers".
* TitleDecodable uses when you know the Parent object has multiple dynamic key-value pair
* In this case Parent object is "phone_numbers" object in the json, and "+94772222222": { body }, "+94772222222": { body } are the multiple dynamic key-value pairs
* Multiple dynamic key-value pair are mapped into PhoneNumber array
*/
struct PhoneNumberDetails: TitleDecodable {
let phoneNumbers: [PhoneNumber]
init(title: String, elements: [PhoneNumber]) {
self.phoneNumbers = elements
}
}
struct PhoneNumber: Decodable {
let number: String
let isActive: Bool
enum CodingKeys: String, CodingKey {
case isActive
}
init(from decoder: Decoder) throws {
self.number = try decoder.currentTitle()
let container = try decoder.container(keyedBy: CodingKeys.self)
self.isActive = try container.decode(Bool.self, forKey: .isActive)
}
}
Focus on how the Json has transformed into the Object structure. Here is the mechanism extracted and improved from Rob's answer.
import Foundation
/*
* This is to handle unknown keys.
* Convert Keys with any String value to CodingKeys
*/
struct TitleKey: CodingKey {
let stringValue: String
init?(stringValue: String) { self.stringValue = stringValue }
var intValue: Int? { return nil }
init?(intValue: Int) { return nil }
}
extension Decoder {
/*
* Decode map into object array that is type of Element
* [Key: Element] -> [Element]
* This will be used when the keys are dynamic and have multiple keys
* Within type Element we can embed relevant Key using => 'try decoder.currentTitle()'
* So you can access Key using => 'element.key'
*/
func decodeMultipleDynamicTitledElements<Element: Decodable>(_ type: Element.Type) throws -> [Element] {
var decodables: [Element] = []
let titles = try container(keyedBy: TitleKey.self)
for title in titles.allKeys {
if let element = try? titles.decode(Element.self, forKey: title) {
decodables.append(element)
}
}
return decodables
}
/*
* Decode map into optional object that is type of Element
* [Key: Element] -> Element?
* This will be used when the keys are dynamic and when you're sure there'll be only one key-value pair
* Within type Element we can embed relevant Key using => 'try decoder.currentTitle()'
* So you can access Key using => 'element.key'
*/
func decodeSingleDynamicTitledElement<Element: Decodable>(_ type: Element.Type) throws -> Element? {
let titles = try container(keyedBy: TitleKey.self)
for title in titles.allKeys {
if let element = try? titles.decode(Element.self, forKey: title) {
return element
}
}
return nil
}
/*
* Decode map key-value pair into optional object that is type of Element
* Key: Element -> Element?
* This will be used when the root key is known, But the value is constructed with Maps where the keys can be Unknown
*/
func decodeStaticTitledElement<Element: Decodable>(with key: TitleKey, _ type: Element.Type) throws -> Element? {
let titles = try container(keyedBy: TitleKey.self)
if let element = try? titles.decode(Element.self, forKey: key) {
return element
}
return nil
}
/*
* This will be used to know where the Element is in the Object tree
* Returns the Key of the Element which was mapped to
*/
func currentTitle() throws -> String {
guard let titleKey = codingPath.last as? TitleKey else {
throw DecodingError.dataCorrupted(.init(codingPath: codingPath, debugDescription: "Not in titled container"))
}
return titleKey.stringValue
}
}
/*
* Class that implements this Protocol, contains an array of Element Objects,
* that will be mapped from a 'Key1: [Key2: Element]' type of map.
* This will be used when the Key2 is dynamic and have multiple Key2 values
* Key1 -> Key1: TitleDecodable
* [Key2: Element] -> Key1_instance.elements
* Key2 -> Key1_instance.elements[index].key2
*/
protocol TitleDecodable: Decodable {
associatedtype Element: Decodable
init(title: String, elements: [Element])
}
extension TitleDecodable {
init(from decoder: Decoder) throws {
self.init(title: try decoder.currentTitle(), elements: try decoder.decodeMultipleDynamicTitledElements(Element.self))
}
}
/*
* Class that implements this Protocol, contains a variable which is type of Element,
* that will be mapped from a 'Key1: [Key2: Element]' type of map.
* This will be used when the Keys2 is dynamic and have only one Key2-value pair
* Key1 -> Key1: SingleTitleDecodable
* [Key2: Element] -> Key1_instance.element
* Key2 -> Key1_instance.element.key2
*/
protocol SingleTitleDecodable: Decodable {
associatedtype Element: Decodable
init(title: String, element: Element?)
}
extension SingleTitleDecodable {
init(from decoder: Decoder) throws {
self.init(title: try decoder.currentTitle(), element: try decoder.decodeSingleDynamicTitledElement(Element.self))
}
}
In this case we can't create static codable classes for this JSON.
Better use JSON serialization and retrive it.
I am using Swift 4 and trying to parse some JSON data which apparently in some cases can have different type values for the same key, e.g.:
{
"type": 0.0
}
and
{
"type": "12.44591406"
}
I am actually stuck with defining my struct because I cannot figure out how to handle this case because
struct ItemRaw: Codable {
let parentType: String
enum CodingKeys: String, CodingKey {
case parentType = "type"
}
}
throws "Expected to decode String but found a number instead.", and naturally,
struct ItemRaw: Codable {
let parentType: Float
enum CodingKeys: String, CodingKey {
case parentType = "type"
}
}
throws "Expected to decode Float but found a string/data instead." accordingly.
How can I handle this (and similar) cases when defining my struct?
I ran into the same issue when trying to decode/encode the "edited" field on a Reddit Listing JSON response. I created a struct that represents the dynamic type that could exist for the given key. The key can have either a boolean or an integer.
{ "edited": false }
{ "edited": 123456 }
If you only need to be able to decode, just implement init(from:). If you need to go both ways, you will need to implement encode(to:) function.
struct Edited: Codable {
let isEdited: Bool
let editedTime: Int
// Where we determine what type the value is
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
// Check for a boolean
do {
isEdited = try container.decode(Bool.self)
editedTime = 0
} catch {
// Check for an integer
editedTime = try container.decode(Int.self)
isEdited = true
}
}
// We need to go back to a dynamic type, so based on the data we have stored, encode to the proper type
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try isEdited ? container.encode(editedTime) : container.encode(false)
}
}
Inside my Codable class, I then use my struct.
struct Listing: Codable {
let edited: Edited
}
Edit: A more specific solution for your scenario
I recommend using the CodingKey protocol and an enum to store all the properties when decoding. When you create something that conforms to Codable the compiler will create a private enum CodingKeys for you. This lets you decide on what to do based on the JSON Object property key.
Just for example, this is the JSON I am decoding:
{"type": "1.234"}
{"type": 1.234}
If you want to cast from a String to a Double because you only want the double value, just decode the string and then create a double from it. (This is what Itai Ferber is doing, you would then have to decode all properties as well using try decoder.decode(type:forKey:))
struct JSONObjectCasted: Codable {
let type: Double?
init(from decoder: Decoder) throws {
// Decode all fields and store them
let container = try decoder.container(keyedBy: CodingKeys.self) // The compiler creates coding keys for each property, so as long as the keys are the same as the property names, we don't need to define our own enum.
// First check for a Double
do {
type = try container.decode(Double.self, forKey: .type)
} catch {
// The check for a String and then cast it, this will throw if decoding fails
if let typeValue = Double(try container.decode(String.self, forKey: .type)) {
type = typeValue
} else {
// You may want to throw here if you don't want to default the value(in the case that it you can't have an optional).
type = nil
}
}
// Perform other decoding for other properties.
}
}
If you need to store the type along with the value, you can use an enum that conforms to Codable instead of the struct. You could then just use a switch statement with the "type" property of JSONObjectCustomEnum and perform actions based upon the case.
struct JSONObjectCustomEnum: Codable {
let type: DynamicJSONProperty
}
// Where I can represent all the types that the JSON property can be.
enum DynamicJSONProperty: Codable {
case double(Double)
case string(String)
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
// Decode the double
do {
let doubleVal = try container.decode(Double.self)
self = .double(doubleVal)
} catch DecodingError.typeMismatch {
// Decode the string
let stringVal = try container.decode(String.self)
self = .string(stringVal)
}
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
switch self {
case .double(let value):
try container.encode(value)
case .string(let value):
try container.encode(value)
}
}
}
One simple solution is to provide an implementation of init(from:) which attempts to decode the value as a String, and if that fails because the type is wrong, attempt to decode as a Double:
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
do {
self.parentType = try container.decode(String.self, forKey: .parentType)
} catch DecodingError.typeMismatch {
let value = try container.decode(Double.self, forKey: .parentType)
self.parentType = "\(value)"
}
}
I had to decode PHP/MySQL/PDO double value that is given as an String, for this use-case I had to extend the KeyedDecodingContainer, like so:
extension KeyedDecodingContainer {
func decode(forKey key: KeyedDecodingContainer.Key) throws -> Double {
do {
let str = try self.decode(String.self, forKey: key)
if let dbl = Double(str) {
return dbl
}
} catch DecodingError.typeMismatch {
return try self.decode(Double.self, forKey: key)
}
let context = DecodingError.Context(codingPath: self.codingPath,
debugDescription: "Wrong Money Value")
throw DecodingError.typeMismatch(Double.self, context)
}
}
Usage:
let data = """
{"value":"1.2"}
""".data(using: .utf8)!
struct Test: Decodable {
let value: Double
enum CodingKeys: String, CodingKey {
case value
}
init(from decoder: Decoder) throws {
self.value = try decoder.container(keyedBy: CodingKeys.self)
.decode(forKey: CodingKeys.value)
}
}
try JSONDecoder().decode(Test.self, from: data).value
// Out Put Json
{
"software_id": "10",
"name": "Kroll"
},
{
"software_id": 580,
"name": "Synmed"
}
// Codable Struct
struct SoftwareDataModel: Codable {
var softwareId:MyValue?
var name:String?
enum CodingKeys: String, CodingKey{
case softwareId = "software_id"
case name
}
}
MYValue is Codable Struct Which help to to convert your datatype into "String" here I mentions only String and Int datatypes.
enum MyValue: Codable {
case string(String)
var stringValue: String? {
switch self {
case .string(let s):
return s
}
}
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if let x = try? container.decode(String.self) {
self = .string(x)
return
}
if let x = try? container.decode(Int.self) {
self = .string("\(x)")
return
}
throw DecodingError.typeMismatch(MyValue.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Wrong type for MyValue"))
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
switch self {
case .string(let x):
try container.encode(x)
}
}
}
// How to get software_id ?
let softwareId = Struct_object.softwareId?.stringValue ?? "0"
I'm trying to find the best way to Encode/Decode an array of structs conforming to a swift protocol using the new JSONDecoder/Encoder in Swift 4.
I made up a little example to illustrate the problem:
First we have a protocol Tag and some Types that conform to this protocol.
protocol Tag: Codable {
var type: String { get }
var value: String { get }
}
struct AuthorTag: Tag {
let type = "author"
let value: String
}
struct GenreTag: Tag {
let type = "genre"
let value: String
}
Then we have a Type Article which has an Array of Tags.
struct Article: Codable {
let tags: [Tag]
let title: String
}
Finally we encode or decode the Article
let article = Article(tags: [AuthorTag(value: "Author Tag Value"), GenreTag(value:"Genre Tag Value")], title: "Article Title")
let jsonEncoder = JSONEncoder()
let jsonData = try jsonEncoder.encode(article)
let jsonString = String(data: jsonData, encoding: .utf8)
And this is the JSON structure that I like to have.
{
"title": "Article Title",
"tags": [
{
"type": "author",
"value": "Author Tag Value"
},
{
"type": "genre",
"value": "Genre Tag Value"
}
]
}
The problem is that at some point I have to switch on the type property to decode the Array but to Decode the Array I have to know its type.
EDIT:
It's clear to me why Decodable can not work out of the box but at least Encodable should work. The following modified Article struct compiles but crashes with the following error message.
fatal error: Array<Tag> does not conform to Encodable because Tag does not conform to Encodable.: file /Library/Caches/com.apple.xbs/Sources/swiftlang/swiftlang-900.0.43/src/swift/stdlib/public/core/Codable.swift, line 3280
struct Article: Encodable {
let tags: [Tag]
let title: String
enum CodingKeys: String, CodingKey {
case tags
case title
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(tags, forKey: .tags)
try container.encode(title, forKey: .title)
}
}
let article = Article(tags: [AuthorTag(value: "Author Tag"), GenreTag(value:"A Genre Tag")], title: "A Title")
let jsonEncoder = JSONEncoder()
let jsonData = try jsonEncoder.encode(article)
let jsonString = String(data: jsonData, encoding: .utf8)
And this is the relevant part from Codeable.swift
guard Element.self is Encodable.Type else {
preconditionFailure("\(type(of: self)) does not conform to Encodable because \(Element.self) does not conform to Encodable.")
}
Source: https://github.com/apple/swift/blob/master/stdlib/public/core/Codable.swift
The reason why your first example doesn't compile (and your second crashes) is because protocols don't conform to themselves – Tag is not a type that conforms to Codable, therefore neither is [Tag]. Therefore Article doesn't get an auto-generated Codable conformance, as not all of its properties conform to Codable.
Encoding and decoding only the properties listed in the protocol
If you just want to encode and decode the properties listed in the protocol, one solution would be to simply use an AnyTag type-eraser that just holds those properties, and can then provide the Codable conformance.
You can then have Article hold an array of this type-erased wrapper, rather than of Tag:
struct AnyTag : Tag, Codable {
let type: String
let value: String
init(_ base: Tag) {
self.type = base.type
self.value = base.value
}
}
struct Article: Codable {
let tags: [AnyTag]
let title: String
}
let tags: [Tag] = [
AuthorTag(value: "Author Tag Value"),
GenreTag(value:"Genre Tag Value")
]
let article = Article(tags: tags.map(AnyTag.init), title: "Article Title")
let jsonEncoder = JSONEncoder()
jsonEncoder.outputFormatting = .prettyPrinted
let jsonData = try jsonEncoder.encode(article)
if let jsonString = String(data: jsonData, encoding: .utf8) {
print(jsonString)
}
Which outputs the following JSON string:
{
"title" : "Article Title",
"tags" : [
{
"type" : "author",
"value" : "Author Tag Value"
},
{
"type" : "genre",
"value" : "Genre Tag Value"
}
]
}
and can be decoded like so:
let decoded = try JSONDecoder().decode(Article.self, from: jsonData)
print(decoded)
// Article(tags: [
// AnyTag(type: "author", value: "Author Tag Value"),
// AnyTag(type: "genre", value: "Genre Tag Value")
// ], title: "Article Title")
Encoding and decoding all properties of the conforming type
If however you need to encode and decoded every property of the given Tag conforming type, you'll likely want to store the type information in the JSON somehow.
I would use an enum in order to do this:
enum TagType : String, Codable {
// be careful not to rename these – the encoding/decoding relies on the string
// values of the cases. If you want the decoding to be reliant on case
// position rather than name, then you can change to enum TagType : Int.
// (the advantage of the String rawValue is that the JSON is more readable)
case author, genre
var metatype: Tag.Type {
switch self {
case .author:
return AuthorTag.self
case .genre:
return GenreTag.self
}
}
}
Which is better than just using plain strings to represent the types, as the compiler can check that we've provided a metatype for each case.
Then you just have to change the Tag protocol such that it requires conforming types to implement a static property that describes their type:
protocol Tag : Codable {
static var type: TagType { get }
var value: String { get }
}
struct AuthorTag : Tag {
static var type = TagType.author
let value: String
var foo: Float
}
struct GenreTag : Tag {
static var type = TagType.genre
let value: String
var baz: String
}
Then we need to adapt the implementation of the type-erased wrapper in order to encode and decode the TagType along with the base Tag:
struct AnyTag : Codable {
var base: Tag
init(_ base: Tag) {
self.base = base
}
private enum CodingKeys : CodingKey {
case type, base
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let type = try container.decode(TagType.self, forKey: .type)
self.base = try type.metatype.init(from: container.superDecoder(forKey: .base))
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(type(of: base).type, forKey: .type)
try base.encode(to: container.superEncoder(forKey: .base))
}
}
We're using a super encoder/decoder in order to ensure that the property keys for the given conforming type don't conflict with the key used to encode the type. For example, the encoded JSON will look like this:
{
"type" : "author",
"base" : {
"value" : "Author Tag Value",
"foo" : 56.7
}
}
If however you know there won't be a conflict, and want the properties to be encoded/decoded at the same level as the "type" key, such that the JSON looks like this:
{
"type" : "author",
"value" : "Author Tag Value",
"foo" : 56.7
}
You can pass decoder instead of container.superDecoder(forKey: .base) & encoder instead of container.superEncoder(forKey: .base) in the above code.
As an optional step, we could then customise the Codable implementation of Article such that rather than relying on an auto-generated conformance with the tags property being of type [AnyTag], we can provide our own implementation that boxes up a [Tag] into an [AnyTag] before encoding, and then unbox for decoding:
struct Article {
let tags: [Tag]
let title: String
init(tags: [Tag], title: String) {
self.tags = tags
self.title = title
}
}
extension Article : Codable {
private enum CodingKeys : CodingKey {
case tags, title
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.tags = try container.decode([AnyTag].self, forKey: .tags).map { $0.base }
self.title = try container.decode(String.self, forKey: .title)
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(tags.map(AnyTag.init), forKey: .tags)
try container.encode(title, forKey: .title)
}
}
This then allows us to have the tags property be of type [Tag], rather than [AnyTag].
Now we can encode and decode any Tag conforming type that's listed in our TagType enum:
let tags: [Tag] = [
AuthorTag(value: "Author Tag Value", foo: 56.7),
GenreTag(value:"Genre Tag Value", baz: "hello world")
]
let article = Article(tags: tags, title: "Article Title")
let jsonEncoder = JSONEncoder()
jsonEncoder.outputFormatting = .prettyPrinted
let jsonData = try jsonEncoder.encode(article)
if let jsonString = String(data: jsonData, encoding: .utf8) {
print(jsonString)
}
Which outputs the JSON string:
{
"title" : "Article Title",
"tags" : [
{
"type" : "author",
"base" : {
"value" : "Author Tag Value",
"foo" : 56.7
}
},
{
"type" : "genre",
"base" : {
"value" : "Genre Tag Value",
"baz" : "hello world"
}
}
]
}
and can then be decoded like so:
let decoded = try JSONDecoder().decode(Article.self, from: jsonData)
print(decoded)
// Article(tags: [
// AuthorTag(value: "Author Tag Value", foo: 56.7000008),
// GenreTag(value: "Genre Tag Value", baz: "hello world")
// ],
// title: "Article Title")
Inspired by #Hamish answer. I found his approach reasonable, however few things might be improved:
Mapping array [Tag] to and from [AnyTag] in Article leave us without auto-generated Codable conformance
It's not possible to have same code for coding/encoding array of base class, since static var type can't be overridden in subclass. (for example if Tag would be super class of AuthorTag & GenreTag)
Most importantly this code can't be reused for another Type, you required to create new AnyAnotherType wrapper and it's internal coding/encoding.
I made slightly different solution, instead of wrapping each element of array, it's possible to make wrapper on entire array:
struct MetaArray<M: Meta>: Codable, ExpressibleByArrayLiteral {
let array: [M.Element]
init(_ array: [M.Element]) {
self.array = array
}
init(arrayLiteral elements: M.Element...) {
self.array = elements
}
enum CodingKeys: String, CodingKey {
case metatype
case object
}
init(from decoder: Decoder) throws {
var container = try decoder.unkeyedContainer()
var elements: [M.Element] = []
while !container.isAtEnd {
let nested = try container.nestedContainer(keyedBy: CodingKeys.self)
let metatype = try nested.decode(M.self, forKey: .metatype)
let superDecoder = try nested.superDecoder(forKey: .object)
let object = try metatype.type.init(from: superDecoder)
if let element = object as? M.Element {
elements.append(element)
}
}
array = elements
}
func encode(to encoder: Encoder) throws {
var container = encoder.unkeyedContainer()
try array.forEach { object in
let metatype = M.metatype(for: object)
var nested = container.nestedContainer(keyedBy: CodingKeys.self)
try nested.encode(metatype, forKey: .metatype)
let superEncoder = nested.superEncoder(forKey: .object)
let encodable = object as? Encodable
try encodable?.encode(to: superEncoder)
}
}
}
Where Meta is generic protocol:
protocol Meta: Codable {
associatedtype Element
static func metatype(for element: Element) -> Self
var type: Decodable.Type { get }
}
Now, storing tags will look like:
enum TagMetatype: String, Meta {
typealias Element = Tag
case author
case genre
static func metatype(for element: Tag) -> TagMetatype {
return element.metatype
}
var type: Decodable.Type {
switch self {
case .author: return AuthorTag.self
case .genre: return GenreTag.self
}
}
}
struct AuthorTag: Tag {
var metatype: TagMetatype { return .author } // keep computed to prevent auto-encoding
let value: String
}
struct GenreTag: Tag {
var metatype: TagMetatype { return .genre } // keep computed to prevent auto-encoding
let value: String
}
struct Article: Codable {
let title: String
let tags: MetaArray<TagMetatype>
}
Result JSON:
let article = Article(title: "Article Title",
tags: [AuthorTag(value: "Author Tag Value"),
GenreTag(value:"Genre Tag Value")])
{
"title" : "Article Title",
"tags" : [
{
"metatype" : "author",
"object" : {
"value" : "Author Tag Value"
}
},
{
"metatype" : "genre",
"object" : {
"value" : "Genre Tag Value"
}
}
]
}
And in case you want JSON to look even prettier:
{
"title" : "Article Title",
"tags" : [
{
"author" : {
"value" : "Author Tag Value"
}
},
{
"genre" : {
"value" : "Genre Tag Value"
}
}
]
}
Add to Meta protocol
protocol Meta: Codable {
associatedtype Element
static func metatype(for element: Element) -> Self
var type: Decodable.Type { get }
init?(rawValue: String)
var rawValue: String { get }
}
And replace CodingKeys with:
struct MetaArray<M: Meta>: Codable, ExpressibleByArrayLiteral {
let array: [M.Element]
init(array: [M.Element]) {
self.array = array
}
init(arrayLiteral elements: M.Element...) {
self.array = elements
}
struct ElementKey: CodingKey {
var stringValue: String
init?(stringValue: String) {
self.stringValue = stringValue
}
var intValue: Int? { return nil }
init?(intValue: Int) { return nil }
}
init(from decoder: Decoder) throws {
var container = try decoder.unkeyedContainer()
var elements: [M.Element] = []
while !container.isAtEnd {
let nested = try container.nestedContainer(keyedBy: ElementKey.self)
guard let key = nested.allKeys.first else { continue }
let metatype = M(rawValue: key.stringValue)
let superDecoder = try nested.superDecoder(forKey: key)
let object = try metatype?.type.init(from: superDecoder)
if let element = object as? M.Element {
elements.append(element)
}
}
array = elements
}
func encode(to encoder: Encoder) throws {
var container = encoder.unkeyedContainer()
try array.forEach { object in
var nested = container.nestedContainer(keyedBy: ElementKey.self)
let metatype = M.metatype(for: object)
if let key = ElementKey(stringValue: metatype.rawValue) {
let superEncoder = nested.superEncoder(forKey: key)
let encodable = object as? Encodable
try encodable?.encode(to: superEncoder)
}
}
}
}
Drawn from the accepted answer, I ended up with the following code that can be pasted into an Xcode Playground. I used this base to add a codable protocol to my app.
The output looks like this, without the nesting mentioned in the accepted answer.
ORIGINAL:
▿ __lldb_expr_33.Parent
- title: "Parent Struct"
▿ items: 2 elements
▿ __lldb_expr_33.NumberItem
- commonProtocolString: "common string from protocol"
- numberUniqueToThisStruct: 42
▿ __lldb_expr_33.StringItem
- commonProtocolString: "protocol member string"
- stringUniqueToThisStruct: "a random string"
ENCODED TO JSON:
{
"title" : "Parent Struct",
"items" : [
{
"type" : "numberItem",
"numberUniqueToThisStruct" : 42,
"commonProtocolString" : "common string from protocol"
},
{
"type" : "stringItem",
"stringUniqueToThisStruct" : "a random string",
"commonProtocolString" : "protocol member string"
}
]
}
DECODED FROM JSON:
▿ __lldb_expr_33.Parent
- title: "Parent Struct"
▿ items: 2 elements
▿ __lldb_expr_33.NumberItem
- commonProtocolString: "common string from protocol"
- numberUniqueToThisStruct: 42
▿ __lldb_expr_33.StringItem
- commonProtocolString: "protocol member string"
- stringUniqueToThisStruct: "a random string"
Paste into your Xcode project or Playground and customize to your liking:
import Foundation
struct Parent: Codable {
let title: String
let items: [Item]
init(title: String, items: [Item]) {
self.title = title
self.items = items
}
enum CodingKeys: String, CodingKey {
case title
case items
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(title, forKey: .title)
try container.encode(items.map({ AnyItem($0) }), forKey: .items)
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
title = try container.decode(String.self, forKey: .title)
items = try container.decode([AnyItem].self, forKey: .items).map { $0.item }
}
}
protocol Item: Codable {
static var type: ItemType { get }
var commonProtocolString: String { get }
}
enum ItemType: String, Codable {
case numberItem
case stringItem
var metatype: Item.Type {
switch self {
case .numberItem: return NumberItem.self
case .stringItem: return StringItem.self
}
}
}
struct NumberItem: Item {
static var type = ItemType.numberItem
let commonProtocolString = "common string from protocol"
let numberUniqueToThisStruct = 42
}
struct StringItem: Item {
static var type = ItemType.stringItem
let commonProtocolString = "protocol member string"
let stringUniqueToThisStruct = "a random string"
}
struct AnyItem: Codable {
var item: Item
init(_ item: Item) {
self.item = item
}
private enum CodingKeys : CodingKey {
case type
case item
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(type(of: item).type, forKey: .type)
try item.encode(to: encoder)
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let type = try container.decode(ItemType.self, forKey: .type)
self.item = try type.metatype.init(from: decoder)
}
}
func testCodableProtocol() {
var items = [Item]()
items.append(NumberItem())
items.append(StringItem())
let parent = Parent(title: "Parent Struct", items: items)
print("ORIGINAL:")
dump(parent)
print("")
let jsonEncoder = JSONEncoder()
jsonEncoder.outputFormatting = .prettyPrinted
let jsonData = try! jsonEncoder.encode(parent)
let jsonString = String(data: jsonData, encoding: .utf8)!
print("ENCODED TO JSON:")
print(jsonString)
print("")
let jsonDecoder = JSONDecoder()
let decoded = try! jsonDecoder.decode(type(of: parent), from: jsonData)
print("DECODED FROM JSON:")
dump(decoded)
print("")
}
testCodableProtocol()
Why wouldn't you use enums for the type of the tag?
struct Tag: Codable {
let type: TagType
let value: String
enum TagType: String, Codable {
case author
case genre
}
}
Then you can encode like try? JSONEncoder().encode(tag) or decode like let tags = try? JSONDecoder().decode([Tag].self, from: jsonData) and do any sort of processing as filtering the tags by type. You can do the same for the Article struct as well:
struct Tag: Codable {
let type: TagType
let value: String
enum TagType: String, Codable {
case author
case genre
}
}
struct Article: Codable {
let tags: [Tag]
let title: String
enum CodingKeys: String, CodingKey {
case tags
case title
}
}
I took the accepted answer from #Hamish, which is excellent, and generalized it a bit. Maybe useful to others, so posting it here...
First, setup reusable types similar to AnyTag and TagType.
protocol ConcreteTypeID: Codable {
var concreteType: any CodableExistential.Type { get }
}
protocol CodableExistential: Codable {
associatedtype TypeID: ConcreteTypeID
var concreteTypeId: TypeID { get }
}
struct ExistentialBox<TypeID: ConcreteTypeID>: Codable {
var existential: any CodableExistential
private enum CodingKey: Swift.CodingKey {
case concreteTypeId
}
init(_ existential: any CodableExistential) {
self.existential = existential
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKey.self)
let type = try container.decode(TypeID.self, forKey: .concreteTypeId)
self.existential = try type.concreteType.init(from: decoder)
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKey.self)
try container.encode(existential.concreteTypeId, forKey: .concreteTypeId)
try existential.encode(to: encoder)
}
}
Now have your concrete types make use of these.
protocol Vehicle: CodableExistential {
var maker: String { get }
}
struct Car: Vehicle {
var concreteTypeId: VehicleTypeID { .car }
var maker: String
var numberOfPassengers: Int
}
struct Truck: Vehicle {
var concreteTypeId: VehicleTypeID { .truck }
var maker: String
}
enum VehicleTypeID: ConcreteTypeID {
case car, truck
var concreteType: any CodableExistential.Type {
switch self {
case .car:
return Car.self
case .truck:
return Truck.self
}
}
}
Lastly, encode/decode your types.
struct Fleet: Codable {
var vehicles: [any Vehicle]
enum CodingKey: Swift.CodingKey { case vehicles }
init(vehicles: [any Vehicle]) {
self.vehicles = vehicles
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKey.self)
let boxes = try container.decode([ExistentialBox<VehicleTypeID>].self, forKey: .vehicles)
vehicles = boxes.map { $0.existential as! any Vehicle }
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKey.self)
let boxes = vehicles.map { ExistentialBox<VehicleTypeID>($0) }
try container.encode(boxes, forKey: .vehicles)
}
}
let fleet = Fleet(vehicles: [Car(maker: "Toyota", numberOfPassengers: 2), Truck(maker: "Mack")])
let data = try JSONEncoder().encode(fleet)
let unpackedFleet = try JSONDecoder().decode(Fleet.self, from: data)
I'm not super happy with the cast used in the decode method of Fleet, but attempts to avoid that by changing the generics were met with classic errors like "any Vehicle cannot conform to Vehicle". If someone can find a better way, would love to hear it.