Closed lohenyumnam closed 6 years ago
@lohenyumnam Why do you need this feature?
If the structure of our Swift type differs from the structure of its encoded form, we can provide a custom implementation of Encodable and Decodable to define our own encoding and decoding logic.
for example
the server sends JSON data as below
{
"Id": "103",
"Name": "Power Boy",
"designation": "Developer",
"photo_url": "images/noimage.jpg",
"TimeIn": "09:42 am",
"TimeOut": "05:35 pm"
}
The id is a string but I want it as Int in my swift type so I have to Manually Implement the code as bellow
struct People: Codable {
var timeOut: String
var timeIn: String
var photo_url: String
var designation: String
var name: String
var id: Int
enum CodingKeys: String, CodingKey {
case timeOut = "TimeOut"
case timeIn = "TimeIn"
case photo_url = "photo_url"
case designation = "designation"
case name = "Name"
case id = "Id"
}
init(from decoder: Decoder) throws {
let value = try decoder.container(keyedBy: CodingKeys.self)
timeOut = try value.decode(String.self, forKey: CodingKeys.timeOut)
timeIn = try value.decode(String.self, forKey: CodingKeys.timeIn)
photo_url = try value.decode(String.self, forKey: CodingKeys.photo_url)
designation = try value.decode(String.self, forKey: CodingKeys.designation)
name = try value.decode(String.self, forKey: CodingKeys.name)
// This will be auto-generated
//id = try value.decode(String.self, forKey: CodingKeys.id)
// Now all I have to do is add Int
id = Int(try value.decode(String.self, forKey: CodingKeys.id))!
}
}
quicktype already supports decoding stringified integers, but only in C# yet. It's built on data transformers, #466.
Would you be interested in contributing?
@schani Thanks for the reply, but I am not familiar with TypeScript, sorry
TypeScript is pretty easy to pick up, particular if you already know Swift.
I need some more examples of why this is desirable. I understand the use case cited, but this is not a good solution for that issue (we have stringified integer influence).
I'm happy to re-open the issue if more examples of why this feature is desirable are given.
The following example is the extreme case, and will always need a little or more modification from the generated code. I know that this method is all about taking control of how we want to decode the JSON data, but by adding the option to add Method init(from decoder: Decoder)
while generating makes life more simpler
Let take a simple example of the below JSON data,
[
{
"name": "Russ Abbot",
"sex": "Male",
"wife": "Selena Gomez"
},
{
"name": "Demi Lovato",
"sex": "Male",
"husband": "Zak Abel"
},
{
"name": "Toss bot",
"sex": "Male",
"wife": "Lovato Gomez"
},
{
"name": "Demi Lovato",
"sex": "Male",
"husband": "Sak Tom"
}
]
As we can see this JSON contain two different data types, one for male and one for female, where female profile contain Name of Husband and for the male name of the wife
If Quicktype Generate this it will look like this.
typealias Profile = [ProfileElement]
struct ProfileElement: Codable {
let name: String
let sex: String
let wife: String?
let husband: String?
enum CodingKeys: String, CodingKey {
case name = "name"
case sex = "sex"
case wife = "wife"
case husband = "husband"
}
}
ProfileElement(name: "Russ Abbot", sex: "Male", wife: Optional("Selena Gomez"), husband: nil)
ProfileElement(name: "Demi Lovato", sex: "Male", wife: nil, husband: Optional("Zak Abel"))
ProfileElement(name: "Toss bot", sex: "Male", wife: Optional("Lovato Gomez"), husband: nil)
ProfileElement(name: "Demi Lovato", sex: "Male", wife: nil, husband: Optional("Sak Tom"))
when decoding the jsonData profile for both Male and female will contain Husband and Wife, with one of them being nil, which doesn't make sense to me.
So, With the power of init(from decoder: Decoder)
Method what I can do is I will reduce the name of wife and Husband to just one value as a "spouse".
typealias Profile = [ProfileElement]
struct ProfileElement: Codable {
let name: String
let sex: String
let spouse: String
enum CodingKeys: String, CodingKey {
case name, sex
}
enum Husband: String, CodingKey {
case spouse = "husband"
}
enum Wife: String, CodingKey {
case spouse = "wife"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
name = try values.decode(String.self, forKey: CodingKeys.name)
sex = try values.decode(String.self, forKey: CodingKeys.sex)
// Creating CodingKey Decoder Container for Husband and wife
let husbandValue = try decoder.container(keyedBy: Husband.self)
let wifeValue = try decoder.container(keyedBy: Wife.self)
// If the husband container able to decode the "husband name" it will be set to spouse
if let husbandName = try? husbandValue.decode(String.self, forKey: Husband.spouse) {
spouse = husbandName
} else {
// If the wife container able to decode the "wife name" it will be set to spouse
spouse = try! wifeValue.decode(String.self, forKey: Wife.spouse)
}
}
}
Now We can decode the JSON using the below code
let jsonData = """
[
{
"name": "Russ Abbot",
"sex": "Male",
"wife": "Selena Gomez"
},
{
"name": "Demi Lovato",
"sex": "Male",
"husband": "Zak Abel"
},
{
"name": "Toss bot",
"sex": "Male",
"wife": "Lovato Gomez"
},
{
"name": "Demi Lovato",
"sex": "Male",
"husband": "Sak Tom"
}
]
""".data(using: .utf8)!
let profile = try? JSONDecoder().decode(Profile.self, from: jsonData)
profile?.forEach { print($0) }
The Result is now very clean and does not contain unnecessary property.
ProfileElement(name: "Russ Abbot", sex: "Male", spouse: "Selena Gomez")
ProfileElement(name: "Demi Lovato", sex: "Male", spouse: "Zak Abel")
ProfileElement(name: "Toss bot", sex: "Male", spouse: "Lovato Gomez")
ProfileElement(name: "Demi Lovato", sex: "Male", spouse: "Sak Tom")
Lets take this JSON
[{
"swifter": {
"fullName": "Federico Zanetello",
"id": 123456,
"twitter": "http://twitter.com/zntfdr"
}
},
{ "bool": true },
{ "bool": false },
{
"swifter": {
"fullName": "Federico Zanetello",
"id": 123456,
"twitter": "http://twitter.com/zntfdr"
}
}]
According to Quicktype, it generates this code
typealias SwifterOrBool1 = [SwifterOrBool1Element]
struct SwifterOrBool1Element: Codable {
let swifter1: Swifter1?
let bool: Bool?
enum CodingKeys: String, CodingKey {
case swifter1 = "swifter1"
case bool = "bool"
}
}
struct Swifter1: Codable {
let fullName: String
let id: Int
let twitter: String
enum CodingKeys: String, CodingKey {
case fullName = "fullName"
case id = "id"
case twitter = "twitter"
}
}
As we can see the key "swifter1" or "bool" is also included with one of the values being nil when JSON data don't include them.
SwifterOrBool1Element(swifter1: Optional(__lldb_expr_78.Swifter1(fullName: "Federico Zanetello", id: 123456, twitter: "http://twitter.com/zntfdr")), bool: nil)
SwifterOrBool1Element(swifter1: nil, bool: Optional(true))
SwifterOrBool1Element(swifter1: nil, bool: Optional(false))
SwifterOrBool1Element(swifter1: Optional(__lldb_expr_78.Swifter1(fullName: "Federico Zanetello", id: 123456, twitter: "http://twitter.com/zntfdr")), bool: nil)
Now with Method init(from decoder: Decoder)
we can take over the control on what to include while decoding
struct Swifter: Decodable {
let fullName: String
let id: Int
let twitter: URL
}
enum SwifterOrBool: Decodable {
case swifter(Swifter)
case bool(Bool)
}
extension SwifterOrBool {
enum CodingKeys: String, CodingKey {
case swifter, bool
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
if let swifter = try container.decodeIfPresent(Swifter.self, forKey: .swifter) {
self = .swifter(swifter)
} else {
self = .bool(try container.decode(Bool.self, forKey: .bool))
}
}
}
We can Decode the JSON Data with the following code.
let json = """
[{
"swifter": {
"fullName": "Federico Zanetello",
"id": 123456,
"twitter": "http://twitter.com/zntfdr"
}
},
{ "bool": true },
{ "bool": false },
{
"swifter": {
"fullName": "Federico Zanetello",
"id": 123456,
"twitter": "http://twitter.com/zntfdr"
}
}]
""".data(using: .utf8)! // our native (JSON) data
let myEnumArray = try JSONDecoder().decode([SwifterOrBool].self, from: json) // decoding our data
myEnumArray.forEach { print($0) } // decoded!
As we can see the key "swifter" or "bool" are no longer in the output when, when JSON data don't include them. the result is way cleaner.
swifter(__lldb_expr_80.Swifter(fullName: "Federico Zanetello", id: 123456, twitter: http://twitter.com/zntfdr))
bool(true)
bool(false)
swifter(__lldb_expr_80.Swifter(fullName: "Federico Zanetello", id: 123456, twitter: http://twitter.com/zntfdr))
Ok, I can see why you would need this. We have plans for quicktype to handle all of the examples you give, but you might not want to wait that long.
This is relatively to implement, unless there are some special cases I can't think of. Would you be willing to contribute? We're happy to help and answer all your questions.
I also need this for transforming data from JSON to Swift, for example: There are some APIs that provide a "currency" amount as String
in JSON, but I'd like to map it to a NSDecimalNumber
in my Swift code. I have implemented transformers to do so, but if I use quicktype to generate my models I'd need to edit the generated files to add this type of conversion, which defeats the purpose of using a code generator.
We could either have a more flexible template for Swift or use the handlebars approach (discussed in https://github.com/quicktype/quicktype/issues/223), which would make it even more customizable, since anyone would be able to easily create their own templates
Hello Guys. Will guys please add support for Manual Encode and Decode in swift
Example code: