Is it possible that CodingKey can be used for only JSONEncoder and for JSONDecoder use default member names ?
Example I have following structure
var str = """
{
"name": "Endeavor",
"abv": 8.9,
"brewery": "Saint Arnold",
"style": "ipa"
}
"""
enum BeerStyle:String,Codable {
case ipa
case stout
case kolsch
}
struct Beer : Codable {
let name : String
let brewery : String
let style : BeerStyle
let abv : Float
// THIS SHOULD BE USED ONLY FOR JSONEncoder ?
enum CodingKeys:String,CodingKey {
case name
case abv = "alcohol_by_volume"
case brewery = "brewery_name"
case style
}
}
let jsonData = str.data(using: .utf8)!
let decoder = JSONDecoder() // how to to make it not to use Coding key
let beer = try! decoder.decode(Beer.self, from: jsonData)
will not work fine since enum CodingKeys:String,CodingKey is there
Any one can suggest me a idea or link ?
Try this:
Add two enums one for encoding and one for decoding, e.g. EncodingKeys and DecodingKeys
Write custom init(from decoder: Decoder) and encode(to encoder: Encoder) implementations like this.
.
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: EncodingKeys.self)
try container.encode(name, forKey: .name)
// ...
}
and
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: DecodingKeys.self)
name = try values.decode(String.self, forKey: .name)
// ...
}
Update
I tried it with just one implementation of encode(...). You just need to rename the Enum to EncodingKeys.self (or something else). Then implement the encode-function like described above. For decoding the CodingKeys and init-function are being synthesized.
Related
I have gone through many articles but still could not find a best approach to tackle this situation . I am having different models , that are used to be returned on the basis of type of cell . What is the best approach to handle with Any data type (Any consists of more than three different data models ). See my code below
import Foundation
struct OverviewWorkout : Decodable {
enum WorkoutType: String, Codable {
case workout
case coach
case bodyArea
case challenge
case title
case group
case trainer
}
var type: WorkoutType
var data : Any
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
type = try container.decode(WorkoutType.self, forKey: .type)
switch type {
case .workout, .challenge:
data = try container.decode(Workout.self, forKey: .data)
case .coach:
data = try container.decode(CoachInstruction.self, forKey: .data)
case .bodyArea:
data = try container.decode([Workout].self, forKey: .data)
case .title:
data = try container.decode(Title.self, forKey: .data)
case .group:
data = try container.decode([Workout].self, forKey: .data)
// trainer data
case .trainer:
data = try container.decode([Trainer].self, forKey: .data)
}
}
private enum CodingKeys: String, CodingKey {
case type,data
}
}
extension OverviewWorkout {
struct Title: Codable {
let title: String
}
}
You can declare the Type enum with associated values as defined below:
struct OverviewWorkout : Decodable {
var type: WorkoutType
enum WorkoutType: String, Codable {
case workout(data: Workout)
case coach(data: CoachInstruction)
case bodyArea(data: [Workout])
case title(data: Title)
case group(data: [Workout])
case trainer(data: Trainer)
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
type = try container.decode(WorkoutType.self, forKey: .type)
switch type {
case .workout:
let data = try container.decode(Workout.self, forKey: .data)
self = .workout(data: data)
case .trainer:
let data = try container.decode(Trainer.self, forKey: .data)
self = .trainer(data: data)
.
.
.
}
}
}
I was short of time so couldn't compile it but I hope you this will give you an idea. Additionally, Sharing a reference article for you. [:D You might have visited already]
In my custom initializer I'd like to decode a dictionary from JSON and then assign its values to properties in the class. To my surprise, compiler does not allow me to decode the dictionary, I get the error:
Value of protocol type 'Any' cannot conform to 'Decodable'; only struct/enum/class types can conform to protocols
If I try to decode dictionary of type [String: Decodable] the error message says:
Value of protocol type 'Decodable' cannot conform to 'Decodable'; only struct/enum/class types can conform to protocols
My initializer looks like this:
public let total: Int
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
...
if let dict = try container.decodeIfPresent([String: Any].self, forKey: .tracks),
let value = dict["total"] as? Int { // Error is displayed at this line
total = value
} else {
total = 0
}
...
}
When I looked for the answer I found this answer and according to it the code above should not cause any problems.
What you are looking for is nestedContainer. It helps you "skip" a hierarchy level in your JSON. Ie: let's imagine that in your code, it's all in the same level (one struct), but in the JSON, it's not, there is a sub dictionary.
For test purpose, your JSON might look like:
{
"title": "normal",
"tracks": {
"name": "myName",
"total": 3
}
}
If we want in our model:
struct TestStruct: Codable {
let title: String
let name: String
let total: Int
}
We need to use nestedContainer(keyedBy: forKey:):
extension TestStruct {
enum TopLevelKeys: String, CodingKey {
case title
case tracks
}
enum NestedLevelCodingKeys: String, CodingKey {
case name
case total
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: TopLevelKeys.self)
self.title = try container.decode(String.self, forKey: .title)
let subcontainer = try container.nestedContainer(keyedBy: NestedLevelCodingKeys.self, forKey: TopLevelKeys.tracks)
self.name = try subcontainer.decode(String.self, forKey: .name)
self.total = try subcontainer.decode(Int.self, forKey: .total) //You can use here a `decodeIfPresent()` if needed, use default values, etc.
}
}
In your sample, you used decodeIfPresent() for the subdictionary. It's unclear if it was for test purpose, or if the sub dictionary was sometimes not present.
If that's the case and you can have a JSON like this:
{
"title": "normal"
}
Then, before calling nestedContainer(keyedBy: forKey:), use if container.contains(TopLevelKeys.tracks) {} and set default values if needed in the else case.
I have the following json string:
{"weight":[{"bmi":24.75,"date":"2020-01-20","logId":1000,"source":"API","time":"23:59:59","weight":200}]}
I want to convert it to a Swift object in order to access the different values. Here is what I am trying to do, I have these structs setup:
struct FitbitResponseModel: Decodable {
let weight: [FitbitResponseData]
}
struct FitbitResponseData: Decodable {
let bmi: Int
let date: String
let logId: Int
let source: String
let time: String
let weight: Int
}
And then I have this method to decode the json string:
func parseJSON(data: Data) -> FitbitResponseModel? {
var returnValue: FitbitResponseModel?
do {
returnValue = try JSONDecoder().decode(FitbitResponseModel.self, from: data)
} catch {
print("Error took place: \(error.localizedDescription).")
}
return returnValue
}
However when I try to run it I get the error that the data couldn’t be read because it isn’t in the correct format. What am I doing wrong? Any help is appreciated.
Thanks in advance!
change
let bmi: Int
to
let bmi: Double
beacuse it's value is coming out to be 24.75 in your response if any variable type doesn't match to JSON response whole model wouldn't map in Codable protocol (Encodable and Decodable)
Talk to your API developer. 000 is not a valid representation of a number for json. It needs to be either 0 or 0.0. You can lint your json at https://jsonlint.com . If you really need to work around this I suggest doing a string replacement on 000, with 0, before you parse the data.
Json is n't valid because logId value in your json is n't valid.
{
"weight": [{
"bmi": 24.75,
"date": "2020-01-20",
"logId": 100,
"source": "API",
"time": "23:59:59",
"weight": 200
}]
}
One really neat feature of this auto-generated conformance is that if you define an enum in your type called "CodingKeys" (or use a type alias 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.
struct Base: Codable {
let weight : [Weight]?
enum CodingKeys: String, CodingKey {
case weight = "weight"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
weight = try values.decodeIfPresent([Weight].self, forKey: .weight)
}
}
struct Weight : Codable {
let bmi : Double?
let date : String?
let logId : Int?
let source : String?
let time : String?
let weight : Int?
enum CodingKeys: String, CodingKey {
case bmi = "bmi"
case date = "date"
case logId = "logId"
case source = "source"
case time = "time"
case weight = "weight"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
bmi = try values.decodeIfPresent(Double.self, forKey: .bmi)
date = try values.decodeIfPresent(String.self, forKey: .date)
logId = try values.decodeIfPresent(Int.self, forKey: .logId)
source = try values.decodeIfPresent(String.self, forKey: .source)
time = try values.decodeIfPresent(String.self, forKey: .time)
weight = try values.decodeIfPresent(Int.self, forKey: .weight)
}
}
Hope that will help!
or you can use SwiftyJSON lib: https://github.com/SwiftyJSON/SwiftyJSON
I am trying to parse an array of heterogeneous objects using Codable. These objects are also unkeyed as well. I should note that I have the container structure correct, because it DOES loop through and print "it is type1" at all correct times as seen below. I just can't figure out how to access the actual object. Here is my code:
var data: [Any]
public init(from decoder: Decoder) throws {
var container = try! decoder.container(keyedBy: CodingKeys.self).nestedUnkeyedContainer(forKey: .data)
while !container.isAtEnd {
var itemContainer = try container.nestedContainer(keyedBy: CodingKeys.self)
let itemType = try itemContainer.decode(String.self, forKey: .type)
switch itemType {
case "type1":
print("it is type1")
// this does not compile, but is what I need
//let objectOfItem1 = try itemContainer.decode(Type1.self)
// this compiles, but doesn't work because there is no key with these objects
//let objectOfItem1 = try itemContainer.decode(Type1, forKey: .type)
default:
print("test:: it is the default")
}
}
}
private enum CodingKeys: String, CodingKey {
case data
case type
}
And here is the JSON I am trying to decode (many properties committed for clarity):
"contents" : {
"data" : [
{
"type" : "type1",
"id" : "6a406cdd7a9cace5"
},
{
"type" : "type2",
"id" : "ljhdgsouilghoipsu"
}
]
}
How can I correctly get my individual Type1 objects out of this structure?
I think the easy way to get around the heterogenous data is to use an enum as an interim type to wrap your various Item types (which all need to Codable):
To allow myself to test this I've changed your json slightly to give me more heterogenous data for testing. I've used:
let json = """
{
"contents": {
"data": [
{
"type": "type1",
"id": "6a406cdd7a9cace5"
},
{
"type": "type2",
"dbl": 1.01
},
{
"type": "type3",
"int": 5
}
]
}
}
and then created the three final types represented by this json
struct Item1: Codable {
let type: String
let id: String
}
struct Item2: Codable {
let type: String
let dbl: Double
}
struct Item3: Codable {
let type: String
let int: Int
}
To allow decoding the multiple types in a type-safe way (as required by Codable) you need to use a single type that can represent (or wrap) the possible options. An enum with associated values works nicely for this
enum Interim {
case type1 (Item1)
case type2 (Item2)
case type3 (Item3)
case unknown //to handle unexpected json structures
}
So far, so good, but then it gets slightly more complicated when it comes to creating the Interim from the JSON. It will need a CodingKey enum which represents all the possible keys for all the Item# types, and then it will need to decode the JSON linking all these keys to their respective types and data:
extension Interim: Decodable {
private enum InterimKeys: String, CodingKey {
case type
case id
case dbl
case int
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: InterimKeys.self)
let type = try container.decode(String.self, forKey: .type)
switch type {
case "type1":
let id = try container.decode(String.self, forKey: .id)
let item = Item1(type: type, id: id)
self = .type1(item)
case "type2":
let dbl = try container.decode(Double.self, forKey: .dbl)
let item = Item2(type: type, dbl: dbl)
self = .type2(item)
case "type3":
let int = try container.decode(Int.self, forKey: .int)
let item = Item3(type: type, int: int)
self = .type3(item)
default: self = .unknown
}
}
}
This provides the mechanism for decoding the heterogenous components, now we just need to deal with the higher-level keys. As we have a Decodable Interim type this is straightforward:
struct DataArray: Decodable {
var data: [Interim]
}
struct Contents: Decodable {
var contents: DataArray
}
This now means the json can be decoded like this...
let data = Data(json.utf8)
let decoder = JSONDecoder()
do {
let contents = try decoder.decode(Contents.self, from: data)
print(contents)
} catch {
print("Failed to decode JSON")
print(error.localizedDescription)
}
This successfully decodes the data into a nested structure where the major component is the array of Interim types with their associated Item# objects. The above produces the following output, showing these nested types:
Contents(contents: testbed.DataArray(data: [testbed.Interim.type1(testbed.Item1(type: "type1", id: "6a406cdd7a9cace5")), testbed.Interim.type2(testbed.Item2(type: "type2", dbl: 1.01)), testbed.Interim.type3(testbed.Item3(type: "type3", int: 5))]))
I think there should be an even safer way to do this with Type Erasure to provide a more extensible solution, but I've not got my head around that fully yet.
I think you need use this structure:
struct A: Codable {
let contents: B?
enum CodingKeys: String, CodingKey {
case contents
}
struct B: Codable {
let data: [C]?
enum CodingKeys: String, CodingKey {
case data
}
struct C: Codable {
let type : String?
let id : String?
}
}
}
extension A {
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let contents = try container.decodeIfPresent(B.self, forKey: .contents)
self.init(contents: contents)
}
}
I'd like to add to flanker's answer an improvement for cleaner approach to avoid having all possible keys to be stored under Interim's CodingKey. Here is an updated Interim
enum Interim: Decodable {
case item1(Item1)
case item2(Item2)
case item3(Item3)
case unknown
init(from decoder: Decoder) throws {
let typeContainer = try decoder.container(keyedBy: Key.self)
// Fallback for any unsupported types
guard let type = try? typeContainer.decode(ItemType.self, forKey: .type) else {
self = .unknown
return
}
// Let corresponding Decodable Item to be initialized from the same decoder.
switch type {
case .type1: self = .item1(try .init(from: decoder))
case .type2: self = .item2(try .init(from: decoder))
case .type3: self = .item3(try .init(from: decoder))
}
}
/// These are values for Item.type
private enum ItemType: String, Decodable {
case type1
case type2
case type3
}
private enum Key: CodingKey {
case type
}
}
I'm trying to make inherited data model in order to parse it with JSONDecoder.
class FirstClass : Codable {
let firstClassProperty: Int
final let arrayOfInts: [Int]
}
class SecondClass : FirstClass {
let secondClassProperty1: Int
let secondClassProperty2: Int
private enum CodingKeys : String, CodingKey {
case secondClassProperty1, secondClassProperty2
}
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
secondClassProperty1 = try container.decode(Int.self, forKey: .secondClassProperty1)
secondClassProperty2 = try container.decode(Int.self, forKey: .secondClassProperty2)
try super.init(from: decoder)
}
}
I use this JSON for FirstClass:
{
"firstClassProperty": 123,
"arrayOfInts": [
123
]
}
and this for SecondClass:
{
"firstClassProperty": {},
"secondClassProperty1": {},
"secondClassProperty2": {}
}
How can I get rid of arrayOfInts in my subclass but let it be in superclass if keyword final doesn't work in this case?
Here's Playground. Thanks for your answers!
A quick hack is to declare it as optional. For instance:
class FirstClas: Codable {
let firstClassProperty: Int
final let arrayOfInts: [Int]?
}
That would work around a missing arrayOfInts automatically.
Manual solution. Another solution is to implement the Decodable protocol yourself — as you did in SecondClass — and decode arrayOfInts using decodeIfPresent (and use a default value otherwise).
Superclass decoding. By the way, the recommended way to forward the Decoder to the superclass is by using superDecoder() method:
...
let superDecoder = try container.superDecoder()
try super.init(from: superDecoder)
You can use like this :
class FirstClass : Codable {
let firstClassProperty: Int
final let arrayOfInts: [Int]?
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
firstClassProperty = try container.decode(Int.self, forKey: .firstClassProperty)
arrayOfInts = try container.decodeIfPresent([Int].self, forKey: .arrayOfInts)
}
}