Codable Encoder required for NSManagedObject subclass? - swift

func encode(to encoder: Encoder) throws is required for the Codable protocol.
Is there any reason to implement this in your code for an NSManagedObject subclass?
Is there something I'm missing, or should a fatal error simply be returned from this function.
The example subclass is the same for all such subclasses
#objc(Commit)
public class Commit: NSManagedObject, Codable {
public func encode(to encoder: Encoder) throws {
// Note encode is blank here
}
required convenience public init(from decoder: Decoder) throws {
// return the context from the decoder userinfo dictionary
guard let contextUserInfoKey = CodingUserInfoKey.context,
let managedObjectContext = decoder.userInfo[contextUserInfoKey] as? NSManagedObjectContext,
let entity = NSEntityDescription.entity(forEntityName: "Commit", in: managedObjectContext)
else {
fatalError("decode failure")
}
// Super init of the NSManagedObject
self.init(entity: entity, insertInto: managedObjectContext)
let values = try decoder.container(keyedBy: CodingKeys.self)
do {
sha = try values.decode(String.self, forKey: .sha)
url = try values.decode(String.self, forKey: .url)
html_url = try values.decode(String.self, forKey: .html_url)
gitcommit = try values.decode(GitCommit.self, forKey: .gitcommit)
} catch {
print ("error")
}
}
enum CodingKeys: String, CodingKey {
case sha = "sha"
case gitcommit = "commit"
case url = "url"
case html_url = "html_url"
}
}

Adopting Codable implies that you want to decode and encode the objects.
If you have no reason to encode the class adopt only Decodable

Related

Why doesn't my struct encode in the order I declared properties and coding keys?

This is the main struct that encodes properly as far as when I print print(String(data: encoded, encoding: .utf8) as Any)
the struct prints encoded with mainObject printing first then the rest of the variables in the struct but i want it to print: mainAccount, mainObject, reference in that order.
struct T: Codable {
init(){}
let mainAccount = "IMHOTECHPECOM"
let mainObject = mainObject()
let reference = UUID()
// Enum that allows easy encoding
enum CodingKeys: CodingKey {
case mainAccount, mainObject, reference;
}
// function to conform to encodable protocol
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(mainAccount, forKey: .merchantAccount)
try container.encode(mainObject.self, forKey: .mainObject)
try container.encode(reference, forKey: .reference)
}
// conforms with decodable protocol
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
_ = try container.decode(String.self, forKey: .mainAccount)
_ = try container.decode(String.self, forKey: .mainObject)
_ = try container.decode(String.self, forKey: .reference)
}
}
This is the mainObject
struct mainObject: Codable {
var type = "kind"
var identifier: String = ""
var guide: String = ""
init(){}
enum CodingKeys: CodingKey{
case type, identifier, guide;
}
// function to conform to encodable protocol
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(type, forKey: .type)
try container.encode(identifier, forKey: .identifier)
try container.encode(guide, forKey: .guide)
}
// conforms with decodable protocol
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
type = try container.decode(String.self, forKey: .type)
identifier = try container.decode(String.self, forKey: .identifier)
guide = try container.decode(String.self, forKey: .identifier)
}
}
This is the function in the actual view encoding the data from a button press
func getBalance() async {
let encoder = JSONEncoder()
encoder.outputFormatting = [.sortedKeys]
encoder.outputFormatting = [.withoutEscapingSlashes]
encoder.outputFormatting = [.prettyPrinted]
guard let encoded = try? encoder.encode(mainobject) else {
print("Failed to encode")
return
}
let url = URL(string: "https://testurl.com")!
var request = URLRequest(url: url)
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.httpMethod = "POST"
do {
let (response, _) = try await URLSession.shared.upload(for: request, from: encoded)
print(String(data: encoded, encoding: .utf8) as Any)
print(String(data: response, encoding: .utf8) as Any)
} catch {
print("Encoding failed")
}
let _ = try? JSONDecoder().decode(mainObject.self, from: encoded)
In this particular case, the order you're asking for happens to match alphabetical order. So if you add .sortedKeys to your encoder's outputFormatting property, you'll get the order you want:
let encoder = JSONEncoder()
encoder.outputFormatting = [.sortedKeys]
let data = try encoder.encode(myT)
This will affect the order of keys in all objects in the JSON. So your T's keys will be output in the order mainAccount, mainObject, reference, and your mainObject's keys will be output in the order guide, identifier, type.
The general answer is that JSONEncoder doesn't remember the order in which you add keys to a keyed container. Internally, it uses a standard Swift Dictionary to store the keys and values. A Swift Dictionary doesn't guarantee any ordering of its keys, and the order can change each time your program is started.
If you want to guarantee that the order of your keys is preserved, you'll have to write your own Encoder implementation, which is not a trivial task.

Swift Decodable - How to decode nested JSON that has been base64 encoded

I am attempting to decode a JSON response from a third-party API which contains nested/child JSON that has been base64 encoded.
Contrived Example JSON
{
"id": 1234,
"attributes": "eyAibmFtZSI6ICJzb21lLXZhbHVlIiB9",
}
PS "eyAibmFtZSI6ICJzb21lLXZhbHVlIiB9" is { 'name': 'some-value' } base64 encoded.
I have some code that is able to decode this at present but unfortunately I have to reinstanciate an additional JSONDecoder() inside of the init in order to do so, and this is not cool...
Contrived Example Code
struct Attributes: Decodable {
let name: String
}
struct Model: Decodable {
let id: Int64
let attributes: Attributes
private enum CodingKeys: String, CodingKey {
case id
case attributes
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.id = try container.decode(Int64.self, forKey: .id)
let encodedAttributesString = try container.decode(String.self, forKey: .attributes)
guard let attributesData = Data(base64Encoded: encodedAttributesString) else {
fatalError()
}
// HERE IS WHERE I NEED HELP
self.attributes = try JSONDecoder().decode(Attributes.self, from: attributesData)
}
}
Is there anyway to achieve the decoding without instanciating the additional JSONDecoder?
PS: I have no control over the response format and it cannot be changed.
If attributes contains only one key value pair this is the simple solution.
It decodes the base64 encoded string directly as Data – this is possible with the .base64 data decoding strategy – and deserializes it with traditional JSONSerialization. The value is assigned to a member name in the Model struct.
If the base64 encoded string cannot be decoded a DecodingError will be thrown
let jsonString = """
{
"id": 1234,
"attributes": "eyAibmFtZSI6ICJzb21lLXZhbHVlIiB9",
}
"""
struct Model: Decodable {
let id: Int64
let name: String
private enum CodingKeys: String, CodingKey {
case id, attributes
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.id = try container.decode(Int64.self, forKey: .id)
let attributeData = try container.decode(Data.self, forKey: .attributes)
guard let attributes = try JSONSerialization.jsonObject(with: attributeData) as? [String:String],
let attributeName = attributes["name"] else { throw DecodingError.dataCorruptedError(forKey: .attributes, in: container, debugDescription: "Attributes isn't eiter a dicionary or has no key name") }
self.name = attributeName
}
}
let data = Data(jsonString.utf8)
do {
let decoder = JSONDecoder()
decoder.dataDecodingStrategy = .base64
let result = try decoder.decode(Model.self, from: data)
print(result)
} catch {
print(error)
}
I find the question interesting, so here is a possible solution which would be to give the main decoder an additional one in its userInfo:
extension CodingUserInfoKey {
static let additionalDecoder = CodingUserInfoKey(rawValue: "AdditionalDecoder")!
}
var decoder = JSONDecoder()
let additionalDecoder = JSONDecoder() //here you can put the same one, you can add different options, same ones, etc.
decoder.userInfo = [CodingUserInfoKey.additionalDecoder: additionalDecoder]
Because the main method we use from JSONDecoder() is func decode<T>(_ type: T.Type, from data: Data) throws -> T where T : Decodable and I wanted to keep it as such, I created a protocol:
protocol BasicDecoder {
func decode<T>(_ type: T.Type, from data: Data) throws -> T where T : Decodable
}
extension JSONDecoder: BasicDecoder {}
And I made JSONDecoder respects it (and since it already does...)
Now, to play a little and check what could be done, I created a custom one, in the idea of having like you said a XML Decoder, it's basic, and it's just for the fun (ie: do no replicate this at home ^^):
struct CustomWithJSONSerialization: BasicDecoder {
func decode<T>(_ type: T.Type, from data: Data) throws -> T where T : Decodable {
guard let dict = try JSONSerialization.jsonObject(with: data) as? [String: Any] else { fatalError() }
return Attributes(name: dict["name"] as! String) as! T
}
}
So, init(from:):
guard let attributesData = Data(base64Encoded: encodedAttributesString) else { fatalError() }
guard let additionalDecoder = decoder.userInfo[.additionalDecoder] as? BasicDecoder else { fatalError() }
self.attributes = try additionalDecoder.decode(Attributes.self, from: attributesData)
Let's try it now!
var decoder = JSONDecoder()
let additionalDecoder = JSONDecoder()
decoder.userInfo = [CodingUserInfoKey.additionalDecoder: additionalDecoder]
var decoder2 = JSONDecoder()
let additionalDecoder2 = CustomWithJSONSerialization()
decoder2.userInfo = [CodingUserInfoKey.additionalDecoder: additionalDecoder]
let jsonStr = """
{
"id": 1234,
"attributes": "eyAibmFtZSI6ICJzb21lLXZhbHVlIiB9",
}
"""
let jsonData = jsonStr.data(using: .utf8)!
do {
let value = try decoder.decode(Model.self, from: jsonData)
print("1: \(value)")
let value2 = try decoder2.decode(Model.self, from: jsonData)
print("2: \(value2)")
}
catch {
print("Error: \(error)")
}
Output:
$> 1: Model(id: 1234, attributes: Quick.Attributes(name: "some-value"))
$> 2: Model(id: 1234, attributes: Quick.Attributes(name: "some-value"))
After reading this interesting post, I came up with a reusable solution.
You can create a new NestedJSONDecodable protocol which gets also the JSONDecoder in it's initializer:
protocol NestedJSONDecodable: Decodable {
init(from decoder: Decoder, using nestedDecoder: JSONDecoder) throws
}
Implement the decoder extraction technique (from the aforementioned post) together with a new decode(_:from:) function for decoding NestedJSONDecodable types:
protocol DecoderExtractable {
func decoder(for data: Data) throws -> Decoder
}
extension JSONDecoder: DecoderExtractable {
struct DecoderExtractor: Decodable {
let decoder: Decoder
init(from decoder: Decoder) throws {
self.decoder = decoder
}
}
func decoder(for data: Data) throws -> Decoder {
return try decode(DecoderExtractor.self, from: data).decoder
}
func decode<T: NestedJSONDecodable>(_ type: T.Type, from data: Data) throws -> T {
return try T(from: try decoder(for: data), using: self)
}
}
And change your Model struct to conform to NestedJSONDecodable protocol instead of Decodable:
struct Model: NestedJSONDecodable {
let id: Int64
let attributes: Attributes
private enum CodingKeys: String, CodingKey {
case id
case attributes
}
init(from decoder: Decoder, using nestedDecoder: JSONDecoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.id = try container.decode(Int64.self, forKey: .id)
let attributesData = try container.decode(Data.self, forKey: .attributes)
self.attributes = try nestedDecoder.decode(Attributes.self, from: attributesData)
}
}
The rest of your code will remain the same.
You could create a single decoder as a static property of Model, configure it once, and use it for all your Model decoding needs, both externally and internally.
Unsolicited thought:
Honestly, I would only recommend doing that if you're seeing a measurable loss of CPU time or crazy heap growth from the allocation of additional JSONDecoders… they're not heavyweight objects, less than 128 bytes unless there's some trickery I don't understand (which is pretty common though tbh):
let decoder = JSONDecoder()
malloc_size(Unmanaged.passRetained(decoder).toOpaque()) // 128

How to remove data model nil fields from custom encoded/decoded json in Swift

I am trying to find a clean way to remove data model optional attributes if its nil when custom Encoding/Decoding my data model in Swift.
My use case:
import Foundation
public struct Message {
public let txnID: UUID
public var userId: String?
public var messageID: UUID?
public init(txnID: UUID, userId: String? = nil, messageID: UUID? = nil) {
self.txnID = txnID
self.userId = userId
self.messageID = messageID
}
}
extension Message: Codable {
private enum CodingKeys: CodingKey {
case txnID, userId, messageID
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
txnID = try container.decode(UUID.self, forKey: .txnID)
// FIXME: Remove `userId, messageID` if `nil`
self.userId = try? container.decode(String.self, forKey: .userId)
self.messageID = try? container.decode(UUID.self, forKey: .messageID)
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(self.txnID, forKey: .txnID)
// FIXME: Remove `userId, messageID` if `nil`
try container.encode(self.userId, forKey: .userId)
try container.encode(self.messageID, forKey: .messageID)
}
}
/// The test case I have is basically is:
/// 1. Custom encode and decode my data model using `JSONEncoder` and `JSONDecoder`
/// 2. Remove optional attributes from the resulting encoded/decoded values
let msg = Message(txnID: UUID())
guard let encodedMsg = try? JSONEncoder().encode(msg), let jsonMessage = String(data: encodedMsg, encoding: String.Encoding.utf8) else {
fatalError()
}
// Now decode message
guard let origianlMsg = try? JSONDecoder().decode(Message.self, from: encodedMsg) else {
fatalError()
}
print("Encoded Message to json: \(jsonMessage)")
I am getting the following json when encoding my model
Encoded Message to json: {"txnID":"6211905C-8B72-4E19-81F0-F95F983F08CC","userId":null,"messageID":null}
However, I would like to remove null values from my json for nil values.
Encoded Message to json: {"txnID":"50EFB999-C513-4DD0-BD3F-EEAE3F2304E9"}
I found that decodeIfPresent, encodeIfPresent are used for this use case.
try? is no longer used as well to validate those fields if they're present.
extension Message: Codable {
private enum CodingKeys: CodingKey {
case txnID, userId, messageID
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
txnID = try container.decode(UUID.self, forKey: .txnID)
self.userId = try container.decodeIfPresent(String.self, forKey: .userId)
self.messageID = try container.decodeIfPresent(UUID.self, forKey: .messageID)
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(self.txnID, forKey: .txnID)
try container.encodeIfPresent(self.userId, forKey: .userId)
try container.encodeIfPresent(self.messageID, forKey: .messageID)
}
}

Decoding invalid URLs as nil

Before you answer:
I'm aware:
That an empty string is an invalid URL
That I could write a custom decoder for Employee
That I could declare url as a String
What I'm looking for is a better solution for decoding the optional URL itself. I'm hoping there's some Codable magic I'm missing!
So, I have JSON such as
let json = Data("""
{
"name": "Fred",
"url": ""
}
""".utf8)
and a corresponding object that contains an optional URL…
struct Employee: Decodable {
let name: String
let url: URL?
}
As url in the JSON is invalid, I'd like it to decode as nil, rather than throwing an error.
Trying the following doesn't work (it doesn't get called)…
extension Optional where Wrapped == URL {
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
do {
self = try container.decode(URL.self)
} catch {
self = nil
}
}
}
In the past I've used…
struct FailableDecodable<T: Decodable>: Deodable {
let wrapped: T?
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
do {
self.wrapped = try container.decode(T.self)
} catch {
print("Error decoding failable object: \(error)")
self.wrapped = nil
}
}
}
struct Employee: Decodable {
let name: String
let url: FailableDecodable<URL>?
}
but this requires me to continually refer to url.wrapped.
Is there a better solution?
If you are using Swift 5.1, you can use #propertyWrapper:
let json = """
{
"name": "Fred",
"url": ""
}
""".data(using: .utf8)!
#propertyWrapper
struct FailableDecodable<Wrapped: Decodable>: Decodable {
var wrappedValue: Wrapped?
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
wrappedValue = try? container.decode(Wrapped.self)
}
}
struct Employee: Decodable {
let name: String
#FailableDecodable
private(set) var url: URL?
}
let employee = try! JSONDecoder().decode(Employee.self, from: json)
employee.url // nil
Edit — Codable version
If you need the top level struct to be Encodable as well you can use Codable conformance to the property wrapper.
#propertyWrapper
struct FailableDecodable<Wrapped: Codable>: Codable {
var wrappedValue: Wrapped?
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
wrappedValue = try? container.decode(Wrapped.self)
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(wrappedValue)
}
}
If url is nil this will output a JSON with url: null
{"name":"Fred","url":null}
If you wish to don't output the url property when nil you will need to implement a custom encoding (with encode(to:)) in Employee (which would mitigate the benefit of using a property wrapper).
Note: Using the default implementation of encode(to:) (not implementing it) works, but output an empty object when url is nil:
{"name":"Fred","url":{}}
If you get Return from initializer without initializing all stored properties warning for your Codable struct initializer that contains #FailableDecodable below will resolve it.
#propertyWrapper
struct FailableDecodable<Wrapped: Codable>: Codable {
var wrappedValue: Wrapped?
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
wrappedValue = try? container.decode(Wrapped.self)
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(wrappedValue)
}
init() {
self.wrappedValue = nil
}
}

Swift enum Codable with associated types

I have a struct with two types, both enum, one of them with different associated values in each enum case (see code). There's any workaround to avoid the switch on UDPCommand? Every case added makes the switch longer and kind of repeated code. And needs to be done on the encoder again. I'm trying to do it with generics but can't make it work. Thanks
struct UDPMessage {
let command: UDPCommand
var data: UDPCommandData
func jsonString() -> String {
let encoder = JSONEncoder()
guard let data = try? encoder.encode(self) else { return "" }
guard let string = String(data: data, encoding: .utf8) else { return "" }
return string
}
}
enum UDPCommand: String, Codable {
case DISCOVER
case FILTER
case TOGGLE
case SWIPE_CHAT
case FORWARD
}
enum UDPCommandData {
case discover(Discover)
case filter(Filter)
case toggle(Toggle)
case swipe(SwipeChat)
case forwardedMessage(ForwardedMessage)
case unkkown
}
extension UDPMessage: Codable {
private enum CodingKeys: String, CodingKey {
case command, data
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let command = try container.decode(UDPCommand.self, forKey: .command)
switch command {
case .DISCOVER:
let data = try container.decode(Discover.self, forKey: .data)
self.init(command: command, data: UDPCommandData.discover(data))
case .FILTER:
let data = try container.decode(Filter.self, forKey: .data)
self.init(command: command, data: UDPCommandData.filter(data))
case .TOGGLE:
let data = try container.decode(Toggle.self, forKey: .data)
self.init(command: command, data: UDPCommandData.toggle(data))
case .SWIPE_CHAT:
let data = try container.decode(SwipeChat.self, forKey: .data)
self.init(command: command, data: UDPCommandData.swipe(data))
default:
self.init(command: command, data: UDPCommandData.unkkown)
}
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(self.command, forKey: .command)
try self.data.encode(to: encoder)
}
}