JSONDecodable and required init - swift

I've created a network manager that gets a JSON response and converts it in to a Swift object,
protocol HTTPResponse: Codable {
}
class UserResponse: HTTPResponse {
var name: String?
}
let responseObj = try? JSONDecoder().decode(T.self, from: response.data!)
where T is an HTTPResponse decendent
I need to be able to create an 'empty' UserResponse object, an instance where all the class variables are nil
I changed HTTPResponse from protocol to class and added an empty required init() {} to it.
This allows me to invoke T() and get an instance of UserResponse but now JSONDecoder is no longer parsing the JSON, it also creates instances where all variables are nil
How can I achieve the end result where JSONDecoder works well, and I can create stub instances of my HTTPResponse decedent classes?
I'm working with swift 4.2

You can add the initialiser requirement to the protocol:
protocol HTTPResponse: Codable {
init()
}
Then, add a required init() { ... } to UserResponse and all the conformers. Each conformer can decide what a "stub" response means for them.
After that T() will work, given that T is a generic parameter with the constraint <T: HTTPResponse>. This might seem strange as you are initialising a protocol. But actually, since protocols don't conform to themselves, T will never be HTTPResponse, and will always be a concrete class that conforms to HTTPResponse.

Related

Can you add a designated initializer to a Realm Object subclass?

Consider the following:
protocol P {
init(data: Data) throws
}
enum PError: Error {
case invalidData
}
final class O: Object, P {
#Persisted var name: String
init(data: Data) throws {
guard let name = String(data: data, encoding: .utf8) else {
throw PError.invalidData
}
self.name = name
super.init()
}
}
O inherits from Realm Object and conforms to P. To satisfy the requirements of P, O implements a designated initializer.
https://github.com/realm/realm-swift/issues/4889#issuecomment-296301901 quotes some docs that no longer exist (or have moved) which stated:
When creating your model Object subclasses, you may sometimes want to add your own custom initialization methods for added convenience.
Due to some present limitations with Swift introspection, these methods cannot be designated initializers for the class. Instead, they need to be marked as convenience initializers
Did this change at some point, possibly as a result of https://github.com/realm/realm-swift/pull/6294?
While it's possible to satisfy P's requirement with a convenience initializer, I'm interested to learn whether it's strictly necessary or whether a designated initializer is okay as well.

Is it possible for a Swift type to be inferred by "pulling out" a Type value from a generic function's parameter?

Introduction
(Apologies if the title is confusing, but I explain the question better here!)
I'm building a networking library that can perform JSON decoding on its responses.
Host apps adopting this library will create enums conforming to NetLibRoute. All that currently does is enforce the presence of asURL:
public protocol NetLibRoute {
var asURL: URL { get throws }
}
In a host app, I have a routing system that enforces API structure at the compiler-level (via enums and associated values) for each endpoint, like this:
enum Routes: NetLibRoute {
case people(Int?)
// Other routes go here, e.g.:
// case user(Int)
// case search(query: String, limit: Int?)
var asURL: URL {
let host = "https://swapi.dev/"
let urlString: String
switch self {
case let .people(personID):
if let personID {
urlString = host + "api/people/\(personID)"
} else {
urlString = host + "api/people/"
}
// Build other URLs from associated values
}
return URL(string: urlString)!
}
}
I also want each enum to be associated with a certain Codable type. I can do that, of course, by modifying the Route protocol declaration to also require a type conforming to Decodable:
protocol NetLibRoute {
var asURL: URL { get throws }
var decodedType: Decodable.Type { get } // This
}
And a matching computed property in my Routes enum:
var decodedType: Decodable.Type {
switch self {
case .people(_):
return Person.self
// And so on
}
}
The Problem
Currently, my networking code has a declaration something like this:
public static func get<T>(route: NetLibRoute,
type: T.Type) async throws -> T where T: Decodable {
// performing request on route.asURL
// decoding from JSON as T or throwing error
// returning decoded T
}
Which lets me call it like this:
let person = try await NetLib.get(route: Routes.people(1), type: Person.self)
However, this redundancy (and potential human error from mismatching route and type) really irks me. I really want to be able to only pass in a route, and have the resulting type be inferred from there.
Is there some way to get the compiler to somehow check the NetLibRoute enum and check its decodedType property, in order to know what type to use?
Ultimately, I want this networking function to take one parameter (a route) and infer the return type of that route (at compile-time, not with fragile runtime hacks or !s), and return an instance of the type.
Is this possible?
Potential Alternatives?
I'm also open to alternative solutions that may involve moving where the get function is called from.
For example, calling this get function on a route itself to return the type:
let person = try await Routes.people(1).get(type: Person.self) // Works, but not optimal
let person = try await Routes.people(1).get() // What I want
Or even on the type itself, by creating a new protocol in the library, and then extending Decodable to conform to it:
public protocol NetLibFetchable {
static var route: NetLibRoute { get }
}
extension Decodable where Self: NetLibFetchable {
public static func get<T>() async throws -> T where Self == T, T: Decodable {
// Call normal get function using inferred properties
return try await NetLib.get(route: route,
type: T.self)
}
Which indeed lets me call like this:
let person = try await Person.get() // I can't figure out a clean way to pass in properties that the API may want, at least not without once again passing in Routes.people(1), defeating the goal of having Person and Routes.people inherently linked.
While this eliminates the issue of type inference, the route can no longer be customized at call-time, and instead is stuck like this:
extension Person: NetLibFetchable {
public static var route: NetLibRoute {
Routes.people(1) // Can't customize to different ID Ints anymore!
}
}
Which makes this particular example a no-go, and leaves me at a loss.
Appreciation
Anyway, thank you so much for reading, for your time, and for your help.
I really want this library to be as clean as possible for host apps interacting with it, and your help will make that possible.
Are you wedded to the idea of using an enum? If not, you can do pretty much what you want by giving each enum value its own type and using an associated type to do what you want.
public protocol NetLibRoute
{
var asURL: URL { get throws }
associatedtype Decoded: Decodable
}
struct Person: Decodable
{
var name: String
}
struct Login: Decodable
{
var id: String
}
struct People: NetLibRoute
{
typealias Decoded = Person
var id: Int
var asURL: URL { return URL(filePath: "/") }
}
struct User: NetLibRoute
{
typealias Decoded = Login
var id: String
var asURL: URL { return URL(filePath: "/") }
}
func get<N: NetLibRoute>(item: N) throws -> N.Decoded
{
let data = try Data(contentsOf: item.asURL)
return try JSONDecoder().decode(N.Decoded.self, from: data)
}
let thing1 = try get(item: People(id: 1))
let thing2 = try get(item: User(id: "foo"))
Where you might have had a switch before to do different things with different Routes you would now use a function with overloaded arguments.
func doSomething(thing: Person)
{
// do something for a Person
}
func doSomething(thing: Login)
{
// do something else for a Login
}
doSomething(thing: thing1)
doSomething(thing: thing2)
I think the problem lays in this function.
public static func get<T>(route: Route,
type: T.Type) async throws -> T where T: Decodable {
// performing request on route.asURL
// decoding from JSON as T or throwing error
// returning decoded T
}
On the first hand, it uses concretions instead of abstractions. You shouldn't pass a Route here, it should use your protocol NetLibRoute instead.
On the other hand, I think that the type param is not needed. Afaik you can get the Type to Decode with the var:
NetLibRoute.decodedType
Am I missing something on this matter?
Apart from that, I'd rather go with struct instead of enum when trying to implement the Routes (concretions). Enums cannot be extended. So you won't be allowing the creation of new requests in client side, only in the library.
I hope I've helped.
PS: Some time ago I made this repo. Maybe that could help you (specially this class). I used Combine instead of async/await, but it's not relevant to what you need.

Use `self =` in convenience initializers to delegate to JSONDecoder or factory methods in Swift to avoid `Cannot assign to value: 'self' is immutable`

Sometimes in Swift, it may be convenient to write an initializer for a class which delegates to JSONDecoder or a factory method. For example, one might want to write
final class Test: Codable {
let foo: Int
init(foo: Int) {
self.foo = foo
}
func jsonData() throws -> Data {
try JSONEncoder().encode(self)
}
convenience init(fromJSON data: Data) throws {
self = try JSONDecoder().decode(Self.self, from: data)
}
}
let test = Test(foo: 42)
let data = try test.jsonData()
let decodedTest = try Test(fromJSON: data)
print(decodedTest.foo)
but this fails to compile with
Cannot assign to value: 'self' is immutable.
What is the solution to work around this problem?
First, note that this limitation exists only for classes, so the example initializer will work for as-is for structs and enums, but not all situations allow changing a class to one of these types.
This limitation on class initializers is a frequent pain-point that shows up often on this site (for example). There is a thread on the Swift forums discussing this issue, and work has started to add the necessary language features to make the example code above compile, but this is not complete as of Swift 5.4.
From the thread:
Swift's own standard library and Foundation overlay hack around this missing functionality by making classes conform to dummy protocols and using protocol extension initializers where necessary to implement this functionality.
Using this idea to fix the example code yields
final class Test: Codable {
let foo: Int
init(foo: Int) {
self.foo = foo
}
func jsonData() throws -> Data {
try JSONEncoder().encode(self)
}
}
protocol TestProtocol: Decodable {}
extension Test: TestProtocol {}
extension TestProtocol {
init(fromJSON data: Data) throws {
self = try JSONDecoder().decode(Self.self, from: data)
}
}
let test = Test(foo: 42)
let data = try test.jsonData()
let decodedTest = try Test(fromJSON: data)
print(decodedTest.foo)
which works fine. If Test is the only type conforming to TestProtocol, then only Test will get this initializer.
An alternative is to simply extend Decodable or another protocol to which your class conforms, but this may be undesirable if you do not want other types conforming to that protocol to also get your initializer.

Cannot decode JSON using Core Data class [duplicate]

Codable seems a very exciting feature. But I wonder how we can use it in Core Data? In particular, is it possible to directly encode/decode a JSON from/to a NSManagedObject?
I tried a very simple example:
and defined Foo myself:
import CoreData
#objc(Foo)
public class Foo: NSManagedObject, Codable {}
But when using it like this:
let json = """
{
"name": "foo",
"bars": [{
"name": "bar1",
}], [{
"name": "bar2"
}]
}
""".data(using: .utf8)!
let decoder = JSONDecoder()
let foo = try! decoder.decode(Foo.self, from: json)
print(foo)
The compiler failed with this errror:
super.init isn't called on all paths before returning from initializer
and the target file was the file that defined Foo
I guess I probably did it wrong, since I didn't even pass a NSManagedObjectContext, but I have no idea where to stick it.
Does Core Data support Codable?
You can use the Codable interface with CoreData objects to encode and decode data, however it's not as automatic as when used with plain old swift objects. Here's how you can implement JSON Decoding directly with Core Data objects:
First, you make your object implement Codable. This interface must be defined on the object, and not in an extension. You can also define your Coding Keys in this class.
class MyManagedObject: NSManagedObject, Codable {
#NSManaged var property: String?
enum CodingKeys: String, CodingKey {
case property = "json_key"
}
}
Next, you can define the init method. This must also be defined in the class method because the init method is required by the Decodable protocol.
required convenience init(from decoder: Decoder) throws {
}
However, the proper initializer for use with managed objects is:
NSManagedObject.init(entity: NSEntityDescription, into context: NSManagedObjectContext)
So, the secret here is to use the userInfo dictionary to pass in the proper context object into the initializer. To do this, you'll need to extend the CodingUserInfoKey struct with a new key:
extension CodingUserInfoKey {
static let context = CodingUserInfoKey(rawValue: "context")
}
Now, you can just as the decoder for the context:
required convenience init(from decoder: Decoder) throws {
guard let context = decoder.userInfo[CodingUserInfoKey.context!] as? NSManagedObjectContext else { fatalError() }
guard let entity = NSEntityDescription.entity(forEntityName: "MyManagedObject", in: context) else { fatalError() }
self.init(entity: entity, in: context)
let container = decoder.container(keyedBy: CodingKeys.self)
self.property = container.decodeIfPresent(String.self, forKey: .property)
}
Now, when you set up the decoding for Managed Objects, you'll need to pass along the proper context object:
let data = //raw json data in Data object
let context = persistentContainer.newBackgroundContext()
let decoder = JSONDecoder()
decoder.userInfo[.context] = context
_ = try decoder.decode(MyManagedObject.self, from: data) //we'll get the value from another context using a fetch request later...
try context.save() //make sure to save your data once decoding is complete
To encode data, you'll need to do something similar using the encode protocol function.
CoreData is its own persistence framework and, per its thorough documentation, you must use its designated initializers and follow a rather specific path to creating and storing objects with it.
You can still use Codable with it in limited ways just as you can use NSCoding, however.
One way is to decode an object (or a struct) with either of these protocols and transfer its properties into a new NSManagedObject instance you've created per Core Data's docs.
Another way (which is very common) is to use one of the protocols only for a non-standard object you want to store in a managed object's properties. By "non-standard", I mean anything thst doesn't conform to Core Data's standard attribute types as specified in your model. For example, NSColor can't be stored directly as a Managed Object property since it's not one of the basic attribute types CD supports. Instead, you can use NSKeyedArchiver to serialize the color into an NSData instance and store it as a Data property in the Managed Object. Reverse this process with NSKeyedUnarchiver. That's simplistic and there is a much better way to do this with Core Data (see Transient Attributes) but it illustrates my point.
You could also conceivably adopt Encodable (one of the two protocols that compose Codable - can you guess the name of the other?) to convert a Managed Object instance directly to JSON for sharing but you'd have to specify coding keys and your own custom encode implementation since it won't be auto-synthesized by the compiler with custom coding keys. In this case you'd want to specify only the keys (properties) you want to be included.
Hope this helps.
Swift 4.2:
Following casademora's solution,
guard let context = decoder.userInfo[.context] as? NSManagedObjectContext else { fatalError() }
should be
guard let context = decoder.userInfo[CodingUserInfoKey.context!] as? NSManagedObjectContext else { fatalError() }.
This prevents errors that Xcode falsely recognizes as array slice problems.
Edit: Use implicitly unwrapped optionals to remove the need to force unwrap .context every time it is being used.
As an alternative for those who would like to make use of XCode's modern approach to NSManagedObject file generation, I have created a DecoderWrapper class to expose a Decoder object which I then use within my object which conforms to a JSONDecoding protocol:
class DecoderWrapper: Decodable {
let decoder:Decoder
required init(from decoder:Decoder) throws {
self.decoder = decoder
}
}
protocol JSONDecoding {
func decodeWith(_ decoder: Decoder) throws
}
extension JSONDecoding where Self:NSManagedObject {
func decode(json:[String:Any]) throws {
let data = try JSONSerialization.data(withJSONObject: json, options: [])
let wrapper = try JSONDecoder().decode(DecoderWrapper.self, from: data)
try decodeWith(wrapper.decoder)
}
}
extension MyCoreDataClass: JSONDecoding {
enum CodingKeys: String, CodingKey {
case name // For example
}
func decodeWith(_ decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.name = try container.decode(String.self, forKey: .name)
}
}
This is probably only useful for models without any non-optional attributes, but it solves my problem of wanting to use Decodable but also manage relationships and persistence with Core Data without having to manually create all my classes / properties.
Edit: Example of it in use
If I have a json object:
let myjson = [ "name" : "Something" ]
I create the object in Core Data (force cast here for brevity):
let myObject = NSEntityDescription.insertNewObject(forEntityName: "MyCoreDataClass", into: myContext) as! MyCoreDataClass
And I use the extension to have the object decode the json:
do {
try myObject.decode(json: myjson)
}
catch {
// handle any error
}
Now myObject.name is "Something"

Difference between T and Self in generic methods

I’m writing a protocol called JSONDataInitializable, which will enable values to be initialized from Data that contains JSON using JSONDecoder.
Since it’s not possible to explicitly use generics within initializers, I declared a universal, type-agnostic helper method in a protocol extension, which the initializer later calls.
However, I came up with not one, but two ways to write such a method.
(1):
private static func initialize<T: JSONDataInitializable>(from jsonData: Data) throws -> T {
return try JSONDecoder().decode(T.self, from: jsonData)
}
(2):
private static func initialize(from jsonData: Data) throws -> Self {
return try JSONDecoder().decode(Self.self, from: jsonData)
}
Could you explain the difference between these methods? They both seem to produce the same result.
The only visible difference is the protocol conformance part in the first variant. However, the methods are declared in a protocol extension and therefore only available to types which conform to the protocol.
UPD
Here’s the complete protocol declaration:
protocol JSONDataInitializable: Decodable {
init?(from jsonData: Data)
}
extension JSONDataInitializable {
init?(from jsonData: Data) {
do {
self = try Self.initialize(from: jsonData)
} catch {
print(error)
return nil
}
}
// (1)
private static func initialize<T: JSONDataInitializable>(from jsonData: Data) throws -> T {
return try JSONDecoder().decode(T.self, from: jsonData)
}
// ⬆⬆⬆
// OR
// ⬇⬇⬇
// (2)
private static func initialize(from jsonData: Data) throws -> Self {
return try JSONDecoder().decode(Self.self, from: jsonData)
}
}
Let’s say we have a struct called User that’s Decodable. We need to initialize values of User from JSON (stored as Data). With the protocol, initialization works like that:
// Protocol conformance declaration
extension User: JSONDataInitializable { }
// JSON stored as Data
let networkData = ...
// Initialization
let john = User(from: networkData)
Your second implementation, using Self is the one that matches your requirements. You want to create an initializer in a protocol, which you can call on the type you want to initialize. Self in protocol functions refers to the type that you call the specific method on.
On the other hand, the generic implementation would allow you to initialize any types conforming to the protocol, but in your init(from:) method you assign the return value to self, so the generic type parameter T will be inferred as Self. This makes it unnecessary to make the method generic, since on a specific type, T will always be Self.