I have multiple Codeable classes that I want to convert to JSON strings.
class MyCodable: NSObject, Codable {
override init() {
}
func encode(to encoder: Encoder) throws {
}
}
class Category: MyCodable {
required init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self);
//... assign values and call super.init
}
override func encode(to encoder: Encoder) throws {
}
var categoyId: Int64;
var categoryName: String;
enum CodingKeys: String, CodingKey {
case categoryId
case categoryName
}
}
class Product: MyCodable {
required init(from decoder: Decoder) throws {
//..assign values and call super.init
}
override func encode(to encoder: Encoder) throws {
}
var productId: Int64;
var categoryId: Int64;
var productName: String;
enum CodingKeys: String, CodingKey {
case productId
case categoryId
case productName
}
}
I have a utility class that I am using for the conversion.
class JSONUtil: NSObject {
public static func encode(objects: [MyCodable]) -> String {
var json: String = "";
do {
let encoder = JSONEncoder();
encoder.outputFormatting = .prettyPrinted;
let jsonData = try encoder.encode(objects);
json = String(data: jsonData, encoding: String.Encoding.utf8)!
} catch let convertError { json = "[{error: '" + convertError.localizedDescription + "'}]"; }
return json;
}
public static func toJson(object: MyCodable) -> String {
var objects = [MyCodable]();
objects.append(object);
return encode(objects: objects);
}
}
The return value is always "[{}]" no matter what is inside the array of MyCodable objects. While debugging, I can see the values of the Category object are populated.
Since MyCodable has no properties, is that why the JSONEncoder does not print out the Category properties?.. Or why doesn't it print the Category properties?
I am having the same issue with the Product object.
The goal is to avoid having to customize the JSON conversion for every class I want to print to JSON.
Your reasoning is correct, MyCodable has no properties, so there is nothing to read/write. You have actually stumbled across a bug of the swift JSON Encoder and Decoder. They do not handle inheritance well. So your Product and Category class become anonymous MyCodable class that have nothing to be encoded or decoded.
Looking at your code, I see no reason for the MyCodable class. Simply have Product and Category adhere to the Codable protocol. If you need the polymorphism of being able to refer to Product and Category in the same way, define a protoocl that adheres to Codable and have Product and Category adhere to the protocol you defined. In my eyes this is redundant however there could be use cases where it is a plausible solution.
Your mistake is that you're trying to use inheritance instead of generics. Your JSONUtil class should be look like this.
class JSONUtil: NSObject {
public static func encode<T: Encodable>(objects: [T]) -> String {
var json: String = "";
do {
let encoder = JSONEncoder();
encoder.outputFormatting = .prettyPrinted;
let jsonData = try encoder.encode(objects);
json = String(data: jsonData, encoding: String.Encoding.utf8)!
} catch let convertError { json = "[{error: '" + convertError.localizedDescription + "'}]"; }
return json;
}
public static func toJson<T: Encodable>(object: T) -> String {
return encode(objects: [object]);
}
}
I'm not sure why you're creating classes inheriting from NSObject. You may be stuck in the old Objective-C ways 😜 Prefer to use struct over class, unless you always need the object to be copied by reference.
Related
Say we've got a cursor based paginated API where multiple endpoints can be paginated. The response of such an endpoint is always as follows:
{
"nextCursor": "someString",
"PAYLOAD_KEY": <generic response>
}
So the payload always returns a cursor and the payload key depends on the actual endpoint we use. For example if we have GET /users it might be users and the value of the key be an array of objects or we could cal a GET /some-large-object and the key being item and the payload be an object.
Bottom line the response is always an object with a cursor and one other key and it's associated value.
Trying to make this generic in Swift I was thinking of this:
public struct Paginable<Body>: Codable where Body: Codable {
public let body: Body
public let cursor: String?
private enum CodingKeys: String, CodingKey {
case body, cursor
}
}
Now the only issue with this code is that it expects the Body to be accessible under the "body" key which isn't the case.
We could have a struct User: Codable and the paginable specialized as Paginable<[Users]> where the API response object would have the key users for the array.
My question is how can I make this generic Paginable struct work so that I can specify the JSON payload key from the Body type?
The simplest solution I can think of is to let the decoded Body to give you the decoding key:
protocol PaginableBody: Codable {
static var decodingKey: String { get }
}
struct RawCodingKey: CodingKey, Equatable {
let stringValue: String
let intValue: Int?
init(stringValue: String) {
self.stringValue = stringValue
intValue = nil
}
init(intValue: Int) {
stringValue = "\(intValue)"
self.intValue = intValue
}
}
struct Paginable<Body: PaginableBody>: Codable {
public let body: Body
public let cursor: String?
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: RawCodingKey.self)
body = try container.decode(Body.self, forKey: RawCodingKey(stringValue: Body.decodingKey))
cursor = try container.decodeIfPresent(String.self, forKey: RawCodingKey(stringValue: "nextCursor"))
}
}
For example:
let jsonString = """
{
"nextCursor": "someString",
"PAYLOAD_KEY": {}
}
"""
let jsonData = Data(jsonString.utf8)
struct SomeBody: PaginableBody {
static let decodingKey = "PAYLOAD_KEY"
}
let decoder = JSONDecoder()
let decoded = try? decoder.decode(Paginable<SomeBody>.self, from: jsonData)
print(decoded)
Another option is to always take the "other" non-cursor key as the body:
struct Paginable<Body: Codable>: Codable {
public let body: Body
public let cursor: String?
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: RawCodingKey.self)
let cursorKey = RawCodingKey(stringValue: "nextCursor")
cursor = try container.decodeIfPresent(String.self, forKey: cursorKey)
// ! should be replaced with proper decoding error thrown
let bodyKey = container.allKeys.first { $0 != cursorKey }!
body = try container.decode(Body.self, forKey: bodyKey)
}
}
Another possible option is to pass the decoding key directly to JSONDecoder inside userInfo and then access it inside init(from:). That would give you the biggest flexibility but you would have to specify it always during decoding.
You can use generic model with type erasing, for example
struct GenericInfo: Encodable {
init<T: Encodable>(name: String, params: T) {
valueEncoder = {
var container = $0.container(keyedBy: CodingKeys.self)
try container.encode(name, forKey: . name)
try container.encode(params, forKey: .params)
}
}
// MARK: Public
func encode(to encoder: Encoder) throws {
try valueEncoder(encoder)
}
// MARK: Internal
enum CodingKeys: String, CodingKey {
case name
case params
}
let valueEncoder: (Encoder) throws -> Void
}
I am trying to write a generic function which accepts Generic T.Type as function argument, but it is not accepting T.Type as array
public protocol Mappable {
}
class User: Mappable {
var name: String?
}
class MyClass {
func request<T: Mappable>(responseType: T.Type) -> T {
let objT = .....
return objT
}
}
let myCls = MyClass()
myCls.request(responseType: User.self) // Works as expected
myCls.request(responseType: [User].self) // Error: Instance method 'request(responseType:)' requires that '[User]' conform to 'Mappable'
Experiment:
If I replace Mappable with Decodable in above code, it works fine. but I want to use my Protocol and not Decodable
I want to achieve similar to
struct Car: Decodable {
var name: String?
}
let data = "{\"name\": \"Lamborghini\"}".data(using: .utf8)
let dataArray = "[{\"name\": \"Lamborghini\"}, {\"name\": \"Ferrari\"}]".data(using: .utf8)
let car = try! JSONDecoder().decode(Car.self, from: data!) // Will return single object of Car
let cars = try! JSONDecoder().decode([Car].self, from: dataArray!) // Will return Array of Car
I am not able to figure out why it works with Decodable and not with my Mappable Protocol
it's because the array is decodable if it's element type is decodable.
extension Array: Decodable where Element: Decodable {
/// Creates a new array by decoding from the given decoder.
///
/// This initializer throws an error if reading from the decoder fails, or
/// if the data read is corrupted or otherwise invalid.
///
/// - Parameter decoder: The decoder to read data from.
public init(from decoder: Decoder) throws {
self.init()
var container = try decoder.unkeyedContainer()
while !container.isAtEnd {
let element = try container.decode(Element.self)
self.append(element)
}
}
}
you can refer the implementation from https://github.com/apple/swift/blob/master/stdlib/public/core/Codable.swift
I think your function call myCls.request(responseType: [User].self) is failed because T is [User], it's not a Mappable.
I'm working on JSON encode and decode, but some problems are very annoying and I don't know how to use CodingKeys in inherit classes.
I have two classes ResponseBean and ResponseWithObjectBean<T>.
Here is the response class definition:
public class ResponseBean: Codable
{
//This is only sample, I define `CodingKeys` because the property in json is in different name.
private enum CodingKeys: String, CodingKey
{
case intA
case intB
}
public var intA: Int32 = 0
public var intB: Int32 = 0
}
public class ResponseWithObjectBean<T: Codable> : ResponseBean
{
/*
Here I don't know how to define an enum to confirm protocl CondingKey.
I defined an enum named CodingKeys or whatever, they just don't work and
the testMessage and obj are still nil.
But if I implement the init(from decoder: Decoder) construction and manually
pass the coding keys which I defined to the decode function, all works fine.
*/
public var testMessage: String? = nil
public var obj: T? = nil
}
and I will get a user from the response:
public class User: Codable
{
private enum CodingKeys: String, CodingKey
{
case name
case age
}
public var name: String? = nil
public var age: Int32? = nil
}
Here is the test json:
var testJson = """
{
"intA": 10,
"intB": 20,
"testMessage": "This is a test json",
"obj":{
"name": "LiHong",
"age": 11
}
}
"""
The following is how I run:
do{
var responseData = testJson.data(using: .utf8)
var decoder = JSONDecoder()
var response: ResponseWithObjectBean<User> = try decoder.decode(ResponseWithObjectBean<User>.self, from: responseData)
}catch let e{
}
I don't know how to define CodingKeys in ResponseWithObjectBean class, and even I did, it dosen't work at all. But if I implement init(from decoder: Decoder) throws construction and manully pass coding keys I defined in ResponseWithObjectBean, I can get all properties.
This is pretty simple, you just have to do the coding and decoding by hand, in the child class:
public class ResponseWithObjectBean<T: Codable> : ResponseBean {
public var testMessage: String? = nil
public var obj: T? = nil
// Create another CodingKey compliant enum with another name for the new keys
private enum CustomCodingKeys: String, CodingKey {
case testMessage
case obj
}
// Override the decoder
required init(from decoder: Decoder) throws {
try super.init(from: decoder)
let container = try decoder.container(keyedBy: CustomCodingKeys.self)
testMessage = try container.decode(String?.self, forKey: .testMessage)
obj = try container.decode(T?.self, forKey: .obj)
}
// And the coder
public override func encode(to encoder: Encoder) throws {
try super.encode(to: encoder)
var container = encoder.container(keyedBy: CustomCodingKeys.self)
try container.encode(testMessage, forKey: .testMessage)
try container.encode(obj, forKey: .obj)
}
}
This way you can decode and encode the way you want:
let decoder = JSONDecoder()
let response = try decoder.decode(ResponseWithObjectBean<User>.self, from: responseData)
let data = try JSONEncoder().encode(response)
print(String(data: data, encoding: .utf8))
EDIT: In order to prevent you from writing manually all this boilerplate, you can use generation tools like Sourcery: https://github.com/krzysztofzablocki/Sourcery
I'm working with a backend developer that likes to encapsulate json bodies in another object such as data:
Example:
GET: /user/current:
{
data: {
firstName: "Evan",
lastName: "Stoddard"
}
}
I would simply like to just call json decode on the response to get a User struct that I've created but the added data object requires another struct. To get around this I created a generic template class:
struct DecodableData<DecodableType:Decodable>:Decodable {
var data:DecodableType
}
Now I can get my json payload and if I want to get a User struct just get the data property of my template:
let user = JSONDecoder().decode(DecodableData<User>.self, from: jsonData).data
This is all fine and dandy until sometimes, the key, data, isn't always data.
I feel like this is most likely fairly trivial stuff, but is there a way I can add a parameter in my template definition so I can change the enum coding keys as that data key might change?
Something like the following?
struct DecodableData<DecodableType:Decodable, Key:String>:Decodable {
enum CodingKeys: String, CodingKey {
case data = Key
}
var data:DecodableType
}
This way I can pass in the target decodable class along with the key that encapsulates that object.
No need for coding keys. Instead, you need a simple container that parses the JSON as a dictionary that has exactly one key-value pair, discarding the key.
struct Container<T>: Decodable where T: Decodable {
let value: T
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
let dict = try container.decode([String: T].self)
guard dict.count == 1 else {
throw DecodingError.dataCorruptedError(in: container, debugDescription: "expected exactly 1 key value pair, got \(dict.count)")
}
value = dict.first!.value
}
}
If the JSON is empty or has more than one key-value pair, an exception is raised.
Assuming a simple struct such as
struct Foo: Decodable, Equatable {
let a: Int
}
you can parse it regardless of the key:
let foo1 = try! JSONDecoder().decode(
Container<Foo>.self,
from: #"{ "data": { "a": 1 } }"#.data(using: .utf8)!
).value
let foo2 = try! JSONDecoder().decode(
Container<Foo>.self,
from: #"{ "doesn't matter at all": { "a": 1 } }"#.data(using: .utf8)!
).value
foo1 == foo2 // true
This also works for JSON responses that have null as the value, in which case you need to parse it as an optional of your type:
let foo = try! JSONDecoder().decode(
Container<Foo?>.self,
from: #"{ "data": null }"#.data(using: .utf8)!
).value // nil
Try with something like this:
struct GenericCodingKey: CodingKey {
var stringValue: String
init(value: String) {
self.stringValue = value
}
init?(stringValue: String) {
self.stringValue = stringValue
}
var intValue: Int?
init?(intValue: Int) {
return nil
}
}
struct DecodableData<DecodableType: CustomDecodable>: Decodable {
var data: DecodableType
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: GenericCodingKey.self)
data = try container.decode(DecodableType.self, forKey: GenericCodingKey(value: DecodableType.dataKey))
}
}
protocol CustomDecodable: Decodable {
static var dataKey: String { get }
}
extension CustomDecodable {
static var dataKey: String {
return "data" // This is your default
}
}
struct CustomDataKeyStruct: CustomDecodable {
static var dataKey: String = "different"
}
struct NormalDataKeyStruct: CustomDecodable {
//Change Nothing
}
I have a Codable class with a variable that holds a dictionary with String keys and values that may be Strings, Ints or custom structs conforming to Codable. My question is:
How do I define a dictionary with values that are Codable?
I hoped it would be enough to say
var data: [String: Codable] = [:]
but apparently that makes my class no longer conform to Codable. I think the problem here is the same as I had here, where I am passing a protocol rather than an object constrained by the protocol
Using JSON Encoder to encode a variable with Codable as type
So presumably I would need a constrained generic type, something like AnyObject<Codable> but that's not possible.
EDIT: Answer
So since this can't be done as per the accepted answer, I am resorting to a struct with dictionaries for each data type
struct CodableData {
var ints: [String: Int]
var strings: [String: String]
//...
init() {
ints = [:]
strings = [:]
}
}
var data = CodableData()
A Swift collection contains just one type, and for it to be Codable, that type would need to be Codable — not Codable the type, but an adopter of the Codable protocol. You need the type here to be some one specific Codable adopter.
Therefore the only way to do what you're describing would be basically to invent some new type, StringOrIntOrStruct, that wraps a String or an Int or a struct and itself adopts Codable.
But I don't think you're doing to do that, as the effort seems hardly worth it. Basically, you should stop trying to use a heterogeneous Swift collection in the first place; it's completely against the spirit of the language. It's a Bad Smell from the get-go. Rethink your goals.
I think the compiler is simply not smart enough to infer the Codable implementation automatically. So one possible solution is to implement Codable by hand:
class Foo: Codable {
var data: [String:Codable] = [:]
func encode(to encoder: Encoder) throws {
// ...
}
required init(from decoder: Decoder) throws {
// ...
}
}
I don’t know whether you can change the data type to help the compiler do the work for you. At least you can pull the hand-coding to the problematic type:
enum Option: Codable {
case number(Int)
case string(String)
func encode(to encoder: Encoder) throws {
// ...
}
init(from decoder: Decoder) throws {
if let num = try? Int(from: decoder) {
self = .number(num)
} else if let str = try? String(from: decoder) {
self = .string(str)
} else {
throw something
}
}
}
class Foo: Codable {
var data: [String:Option] = [:]
}
Have any of you tried Generics in Swift?
Look:
public struct Response<T> where T : Codable {
let ContentEncoding : String?
let ContentType : String?
let JsonRequestBehavior : Int?
let MaxJsonLength : Int?
let RecursionLimit : Int?
let data : Data?
public struct Data : Codable {
let response : String?
let status : String?
let details : [T]?
}
}