Using JSONencoder to Encode a Variable with Codable as Type

Using JSONEncoder to encode a variable with Codable as type

Use a generic type constrained to Encodable

func saveObject<T : Encodable>(_ object: T, at location: String) {
//Some code

let data = try JSONEncoder().encode(object)

//Some more code
}

Swift: How to pass a codable type as a function input to encode or decode json

You're very close. Your syntax is just slightly incorrect. You want to pass the type as a value, not a value of the type, so this is the signature you need:

static func loadCache<T:Decodable>(fileName:String, type: T.Type) throws -> T{
^^^^^

Swift requires that you be very explicit about passing types as values. It requires adding .self:

let products = try loadCache(fileName: Product.cacheFileName(),
type: [Product].self)
^^^^^

How can I write a function which accepts any object which conforms to Codable

Your signature is incorrect. You don't want a Codable. You want a generic type that conforms to Codable. Specifically, you only really need one that conforms to Encodable:

func sendToServer<Message: Encodable>(message: Message) { ... }

A "Codable" or "Encodable" (the protocols) can't itself be encoded. It doesn't have any information about what to encode. But types that conform to Encodable provide that information.

JSONEncoder won't allow type encoded to primitive value

There is a bug report for this:

https://bugs.swift.org/browse/SR-6163

SR-6163: JSONDecoder cannot decode RFC 7159 JSON

Basically, since RFC-7159, a value like 123 is valid JSON, but JSONDecoder won't support it. You may follow up on the bug report to see any future fixes on this. [The bug was fixed starting in iOS 13.]

#Where it fails#

It fails in the following line of code, where you can see that if the object is not an array nor dictionary, it will fail:

https://github.com/apple/swift-corelibs-foundation/blob/master/Foundation/JSONSerialization.swift#L120

open class JSONSerialization : NSObject {
//...

// top level object must be an Swift.Array or Swift.Dictionary
guard obj is [Any?] || obj is [String: Any?] else {
return false
}

//...
}

#Workaround#

You may use JSONSerialization, with the option: .allowFragments:

let jsonText = "123"
let data = Data(jsonText.utf8)

do {
let myString = try JSONSerialization.jsonObject(with: data, options: .allowFragments)
print(myString)
}
catch {
print(error)
}

Encoding to key-value pairs

Finally, you could also have your JSON objects look like this:

{ "integer": 123456 }

or

{ "string": "potatoe" }

For this, you would need to do something like this:

import Foundation 

enum MyValue {
case integer(Int)
case string(String)
}

extension MyValue: Codable {

enum CodingError: Error {
case decoding(String)
}

enum CodableKeys: String, CodingKey {
case integer
case string
}

init(from decoder: Decoder) throws {

let values = try decoder.container(keyedBy: CodableKeys.self)

if let integer = try? values.decode(Int.self, forKey: .integer) {
self = .integer(integer)
return
}

if let string = try? values.decode(String.self, forKey: .string) {
self = .string(string)
return
}

throw CodingError.decoding("Decoding Failed")
}


func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodableKeys.self)

switch self {
case let .integer(i):
try container.encode(i, forKey: .integer)
case let .string(s):
try container.encode(s, forKey: .string)
}
}

}

let theEncodedValue = try! JSONEncoder().encode(MyValue.integer(123456))
let theEncodedString = String(data: theEncodedValue, encoding: .utf8)
print(theEncodedString!) // { "integer": 123456 }
let theDecodedValue = try! JSONDecoder().decode(MyValue.self, from: theEncodedValue)

How to use Codable encode with object inside object

I'm not sure what constrains you have here, but I would simplify all of this down. Keep the JSON data models as close to the JSON as you can get.

struct ConversationJsonModel: Codable {
var title: String?
var message: MessageJsonModel
var participants: [Int]
var type: String
}

struct MessageJsonModel: Codable {
var body: String
var is_videocall_invite: Int
var attachment: AttachmentJsonModel?
}

struct AttachmentJsonModel: Codable {
var photo: String
var photo_type: String // <-- assuming photo_type is the JSON member name.
}

If you need a view model or some other kind of local data model, then the two parts are separate.

class BaseObject {}

class ConversationPayload: BaseObject {
var title : String? = ""
var messageDict: MessagePayload = MessagePayload()
var participants: [Int32] = []
var type: String = ""

func makeConversationJsonModel() -> ConversationJsonModel {
ConversationJsonModel(title: title,
message: messageDict.makeMessageJsonModel(),
participants: participants.map { Int($0) },
type: type)
}
}

class MessagePayload: BaseObject {
var body : String = ""
var isVideocallInvite: Bool = false
var attachmentsPayload: MessageAttachmentPayload? = nil

func makeMessageJsonModel() -> MessageJsonModel {
MessageJsonModel(body: body,
is_videocall_invite: isVideocallInvite ? 1 : 0,
attachment: attachmentsPayload?.makeAttachmentJsonModel())
}
}

class MessageAttachmentPayload: BaseObject {
var photo : String = ""
var photoType : String = "jpg"

func makeAttachmentJsonModel() -> AttachmentJsonModel {
AttachmentJsonModel(photo: photo, photo_type: photoType)
}
}

Finally, encoding your JSON

let conversationPayload = ConversationPayload()
let json = try? JSONEncoder().encode(conversationPayload.makeConversationJsonModel())

This allows for clean separation between the JSON representation and the payload model. For example, in the JSON, is_videocall_invite is an Int (0 or 1); meanwhile, in the payload model, isVideocallInvite is a Bool.



Related Topics



Leave a reply



Submit