Create a generic Data initializer for Decodable types using Swift - swift

#objcMembers
public class MyResponse: NSObject, Codable {
public let id: String?
public let context: String?
public let results: [MyResult]?
}
What is the proper way to parse MyResponse from Data in class extension?
I tried the following, but got error "Cannot assign to value: 'self' is immutable.
Cannot assign value of type 'MyResponse' to type 'Self'."
extension MyResponse {
public convenience init(data: Data) throws {
self = try JSONDecoder().decode(MyResponse.self, from: data)
}
}

You can extend Decodable protocol and create a generic initializer:
extension Decodable {
public init(data: Data, using decoder: JSONDecoder = JSONDecoder()) throws {
self = try decoder.decode(Self.self, from: data)
}
}

You can't overwrite class itself, but you can init it, init object from json and then assign values/ If take your code - it'll be something like this:
public class MyResponse: Codable {
public var id: String?
public var context: String?
public var results: [MyResult]?
}
public extension MyResponse {
convenience init(data: Data) throws {
self.init()
let object = try JSONDecoder().decode(MyResponse.self, from: data)
self.id = object.id
self.context = object.context
self.results = object.results
}
}
If you really don't need a class it's better to use struct instead of it, and it can be like this:
public struct MyResponse: Codable {
public let id: String?
public let context: String?
public let results: [String]?
}
public extension MyResponse {
init(data: Data) throws {
self = try JSONDecoder().decode(MyResponse.self, from: data)
}
}

Related

How swift implement the default Decodable for struct?

struct Person: Decodable {
let firstName: String
}
var data = """
{"firstName": "Fai"}
""".data(using: .utf8)!
let decoder = JSONDecoder()
let parsed = try decoder.decode(Person.self, from: data)
JSONDecoder will decode the data, which is comfirmed to Decodable protocol.
So I want to know how swift implement this. But I can not get any idea in the source code: https://github.com/apple/swift/blob/main/stdlib/public/core/Codable.swift
The Decodable protocol only need to implement an init(from decoder: Decoder) function.
If I am going to do it, I will make an extension for struct:
extension struct: Decodable {
init(from decoder: Decoder) {...}
}
But when I delete the Decodable on my example, the compiler give errors:
Instance method 'decode(_:from:)' requires that 'Person' conform to 'Decodable'
So this is not the swift way to implement this. How's swift way? And where's the source code?
You can see what the compiler writes for you using -print-ast:
echo 'struct Person: Decodable {
let firstName: String
}' | swiftc -print-ast -
This will output most of the auto-generated code (it should include all of the Codable conformances, but there are a few other kinds of auto-generated code that won't include their implementation):
internal struct Person : Decodable {
internal let firstName: String
private enum CodingKeys : CodingKey {
case firstName
#_implements(Equatable, ==(_:_:)) fileprivate static func __derived_enum_equals(_ a: Person.CodingKeys, _ b: Person.CodingKeys) -> Bool {
private var index_a: Int
switch a {
case .firstName:
index_a = 0
}
private var index_b: Int
switch b {
case .firstName:
index_b = 0
}
return index_a == index_b
}
fileprivate func hash(into hasher: inout Hasher) {
private var discriminator: Int
switch self {
case .firstName:
discriminator = 0
}
hasher.combine(discriminator)
}
private init?(stringValue: String) {
switch stringValue {
case "firstName":
self = Person.CodingKeys.firstName
default:
return nil
}
}
private init?(intValue: Int) {
return nil
}
fileprivate var hashValue: Int {
get {
return _hashValue(for: self)
}
}
fileprivate var intValue: Int? {
get {
return nil
}
}
fileprivate var stringValue: String {
get {
switch self {
case .firstName:
return "firstName"
}
}
}
}
internal init(firstName: String)
internal init(from decoder: Decoder) throws {
#_hasInitialValue private let container: KeyedDecodingContainer<Person.CodingKeys> = try decoder.container(keyedBy: Person.CodingKeys.self)
self.firstName = try container.decode(String.self, forKey: Person.CodingKeys.firstName)
}
}
For the full implementation details, see DerivedConformanceCodable.cpp. Probably of most interest to your question is deriveBodyDecodable_init.

Swift: Insert codable object into Core Data

I'm getting a response from an API and decoding the response like this:
struct MyStuff: Codable {
let name: String
let quantity: Int
let location: String
}
And I have instance an Entity to map MyStuff:
#objc(Stuff)
public class Stuff: NSManagedObject {
}
extension Stuff {
#nonobjc public class func fetchRequest() -> NSFetchRequest<Stuff> {
return NSFetchRequest<Stuff>(entityName: "Stuff")
}
#NSManaged public var name: String?
#NSManaged public var quantity: Int64
#NSManaged public var location: String?
}
My question is, when I have the response of type MyStuff there is a way to loop thru the keys and map the values to core data?
for example:
let myStuff = MyStuff(name: "table", quantity: 1, location: "kitchen")
let myStuff = MyStuff(name: "table", quantity: 1, location: "kitchen")
for chidren in Mirror(reflecting: myStuff).children {
print(chidren.label)
print(chidren.value)
/*
insert values to core data
*/
}
I'll really appreciate your help
A smart solution is to adopt Decodable in Stuff
Write an extension of CodingUserInfoKey and JSONDecoder
extension CodingUserInfoKey {
static let context = CodingUserInfoKey(rawValue: "context")!
}
extension JSONDecoder {
convenience init(context: NSManagedObjectContext) {
self.init()
self.userInfo[.context] = context
}
}
In Stuff adopt Decodable and implement init(from:), it must be implemented in the class, not in the extension
#objc(Stuff)
public class Stuff: NSManagedObject, Decodable {
private enum CodingKeys: String, CodingKey { case name, quantity, location }
public required convenience init(from decoder: Decoder) throws {
guard let context = decoder.userInfo[.context] as? NSManagedObjectContext else { fatalError("Error: context doesn't exist!") }
let entity = NSEntityDescription.entity(forEntityName: "Stuff", in: context)!
self.init(entity: entity, insertInto: context)
let values = try decoder.container(keyedBy: CodingKeys.self)
name = try values.decodeIfPresent(String.self, forKey: .name)
quantity = try values.decodeIfPresent(Int64.self, forKey: .quantity) ?? 0
location = try values.decodeIfPresent(String.self, forKey: .location)
}
}
To decode the JSON you have to initialize the decoder with the convenience initializer
let decoder = JSONDecoder(context: context)
where context is the current NSManagedObjectContext instance.
Now you can create Stuff instances directly from the JSON.
You can store entire object as JSONString if you don't support query for each field.
If you need query for some field then keep that field in entity object.
struct MyStuff: Codable {
let name: String
let quantity: Int
let location: String
}
extension Encodable {
func toString() -> String? {
if let config = try? JSONEncoder().encode(self) {
return String(data: config, encoding: .utf8)
}
return .none
}
}
extension Decodable {
static func map(JSONString: String) -> Self? {
try? JSONDecoder().decode(Self.self, from: JSONString.data(using: .utf8) ?? .init())
}
}
#objc(Stuff)
public class Stuff: NSManagedObject {
}
// Entity with single field (no field base query support)
extension Stuff {
#nonobjc public class func fetchRequest() -> NSFetchRequest<Stuff> {
return NSFetchRequest<Stuff>(entityName: "Stuff")
}
#NSManaged public var myStuffRawJSON: String?
func mapToMyStuff() -> MyStuff? {
MyStuff.map(JSONString: myStuffRawJSON ?? "")
}
}
How to use:
let myStuff = MyStuff(name: "table", quantity: 1, location: "kitchen")
let entity: Stuff //Create entity
entity.myStuffRawJSON = myStuff.toString()
// save your entity

How to create an instance of a Decoder?

There is struct from another module that I would like to initialize. It doesn't have a public init() method but I can create it from decoding JSON.
struct Foo: Codable {
let name: String
let details: String
}
I have created a static function to create an instance
extension Foo {
static func create(name: String) throws -> Self {
let dictionaryRep = ["name": name, "details": ""]
let data = try JSONSerialization.data(withJSONObject: dictionaryRep, options: [])
return try JSONDecoder().decode(Self.self, from: data)
}
}
Is there instead a way to create a new init method to create a new instance?
extension {
public init(name: String) throws {
try self.init(from: <#Decoder#>)
}
}
Because Foo is a struct (not a class), your initializer can assign to self like this:
extension Foo {
init(name: String) throws {
let data = try JSONSerialization.data(withJSONObject: ["name": name, "details": ""])
self = try JSONDecoder().decode(Self.self, from: data)
}
}

Automatic decodable synthesis for decodable property wrappers

Let's say I have decodable property wrapper:
#propertyWrapper
struct OptionalDecodable<Value: Decodable>: Decodable {
var wrappedValue: Value?
}
The compiler does synthesize init for the following
struct Model: Decodable {
#OptionalDecodable private(set) var string: String?
}
To test if this works I just try to decode empty JSON (i.e. "{}")
However, string property is not treated as optional, i.e. when there's no string key I get an error that key was not found.
Is there a work around this?
I'm not sure if this is the best way, but the issue is that wrappedValue type of the property wrapper has to match the property's type, and String is different than String?.
One approach to overcome this is to make the property wrapper generic, but constrain in such a way that would allow you to initialize the type from a String or an Int:
protocol ExpressibleByString {
init(fromString: String)
}
extension String: ExpressibleByString {
init(fromString: String) { self = fromString }
}
extension Optional: ExpressibleByString where Wrapped == String {
init(fromString: String) { self = fromString }
}
#propertyWrapper
struct IntOrString<V: ExpressibleByString & Decodable>: Decodable {
var wrappedValue: V
}
extension IntOrString {
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
do {
let int = try container.decode(Int.self)
wrappedValue = .init(fromString: int.description)
} catch DecodingError.typeMismatch {
wrappedValue = try .init(fromString: container.decode(String.self))
}
}
}
extension KeyedDecodingContainer {
func decode<V: ExpressibleByNilLiteral>(_ t: IntOrString<V>.Type, forKey key: K) throws -> IntOrString<V> {
if let v = try decodeIfPresent(t, forKey: key) {
return v
}
return IntOrString(wrappedValue: nil)
}
}
Then you could use it on both optional and non-optional String:
struct Foo: Decodable {
#IntOrString
var p1: String?
#IntOrString
var p2: String
}

Swift Decode and Encode Custom Types

I'm having difficulties decoding and encoding one of my classes in Swift. I have tried following the Encoding and Decoding Custom Types documentation but with no luck.
My class layout is as follows:
public struct MapLocation: Identifiable, Codable {
#DocumentID public var id: String?
let originLocation: [MapLandmark]
let destinationLocation: [MapLandmark]
enum CodingKeys: String, CodingKey {
case originLocation
case destinationLocation
}
}
import Foundation
import MapKit
struct MapLandmark: Codable {
let placemark: MKPlacemark
var id: UUID {
return UUID()
}
var name: String {
self.placemark.name ?? ""
}
var title: String {
self.placemark.title ?? ""
}
var coordinate: CLLocationCoordinate2D {
self.placemark.coordinate
}
}
I have tried adding the encoding and decoding classes from the above link but keep running into errors. What is the best way to implement a solution?
EDIT:
I forgot to mention the errors I am getting are:
"Type 'MapLandmark' does not conform to protocol 'Decodable'"
"Type 'MapLandmark' does not conform to protocol 'Encodable"
MKPlacemark conforms to NSSecureCoding. You can just use NSKeyedArchiver and NSKeyedUnarchiver to encode/decode it. UUID already conforms to Codable. Try as follow:
import MapKit
extension NSSecureCoding {
func archived() throws -> Data {
try NSKeyedArchiver.archivedData(withRootObject: self, requiringSecureCoding: false)
}
}
extension Data {
func unarchived<T: NSSecureCoding>() throws -> T? {
try NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(self) as? T
}
}
struct MapLandmark: Codable {
let placemark: MKPlacemark
let id: UUID
func encode(to encoder: Encoder) throws {
var unkeyedContainer = encoder.unkeyedContainer()
try unkeyedContainer.encode(placemark.archived())
try unkeyedContainer.encode(id)
}
public init(from decoder: Decoder) throws {
var container = try decoder.unkeyedContainer()
placemark = try container.decode(Data.self).unarchived()!
id = try container.decode(UUID.self)
}
}