Custom Intialiser on Primitive Types for JSONdecoder

Decode a nested object in Swift 5 with custom initializer

This can’t work for multiple reasons. You are trying to decode an array, so your custom decoding implementation from DailyCount won’t be called at all (if it were to compile) since at the top level your JSON contains an object, not an array.

But there is a much simpler solution which doesn’t even require implementing Decodable yourself.

You can create a generic wrapper struct for your outer object and use that with whatever payload type you need:

 struct Wrapper<Payload: Decodable>: Decodable {
var data: Payload
}

You then can use this to decode your array of DailyCount structs:

let reports = try JSONDecoder().decode(Wrapper<[DailyCount]>.self, from: data).data

This can be made even more transparent by creating an extension on JSONDecoder:

extension JSONDecoder {
func decode<T: Decodable>(payload: T.Type, from data: Data) throws -> T {
try decode(Wrapper<T>.self, from: data).data
}
}

Is it possible to pass multiple data types inside a JsonDecoder?

Make the protocol conform to Decodable and use a generic function

protocol CommonStruct: Decodable {}

func decode<T: CommonStruct>(from data: Data) throws -> T {
try JSONDecoder().decode(T.self, from: data)
}

Or if you have no other use for your protocol then you can skip it and do

func decode<T: Decodable>(from data: Data) throws -> T {
try JSONDecoder().decode(T.self, from: data)
}

Create a generic Data initializer for Decodable types using Swift

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)
}
}

Swift custom decodable initializer without CodingKeys

The auto-generation for CodingKeys is really weird. The scope and availability of it changes based on what members you have.

Say you just have a Decodable. These compile:

struct Decodable: Swift.Decodable {
static var codingKeysType: CodingKeys.Type { CodingKeys.self }
}
struct Decodable: Swift.Decodable {
static func `init`(from decoder: Decoder) throws -> Self {
_ = CodingKeys.self
return self.init()
}
}

…and you can put them together, if you add private.

struct Decodable: Swift.Decodable {
private static var codingKeysType: CodingKeys.Type { CodingKeys.self }

static func `init`(from decoder: Decoder) throws -> Self {
_ = CodingKeys.self
return self.init()
}
}

…But make that func an initializer, and again, no compilation.

struct Decodable: Swift.Decodable {
private static var codingKeysType: CodingKeys.Type { CodingKeys.self }

init(from decoder: Decoder) throws {
_ = CodingKeys.self
}
}

You can change it to be fully Codable, not just Decodable

struct Decodable: Codable {
private static var codingKeysType: CodingKeys.Type { CodingKeys.self }

init(from decoder: Decoder) throws {
_ = CodingKeys.self
}
}

But then you can't use CodingKeys at type scope, so the property won't compile.

Considering you probably don't need such a property, just use Codable, file a bug with Apple referencing this answer, and hopefully we can all switch to Decodable when they fix it. /p>

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

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.

multiple types in Codable

I definitely agree with @vadian. What you have is an optional rating. IMO this is a perfect scenario for using a propertyWrapper. This would allow you to use this Rated type with any model without having to manually implement a custom encoder/decoder to each model:

@propertyWrapper
struct RatedDouble: Codable {

var wrappedValue: Double?
init(wrappedValue: Double?) {
self.wrappedValue = wrappedValue
}

private struct Rated: Decodable {
let value: Double
}

public init(from decoder: Decoder) throws {
do {
wrappedValue = try decoder.singleValueContainer().decode(Rated.self).value
} catch DecodingError.typeMismatch {
let bool = try decoder.singleValueContainer().decode(Bool.self)
guard !bool else {
throw DecodingError.dataCorrupted(.init(codingPath: decoder.codingPath, debugDescription: "Corrupted data"))
}
wrappedValue = nil
}
}

public func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
guard let double = wrappedValue else {
try container.encode(false)
return
}
try container.encode(["value": double])
}
}

Usage:

struct AccountState: Codable {
let id: Int?
let favorite: Bool?
let watchlist: Bool?
@RatedDouble var rated: Double?
}


let json1 = #"{"id":550,"favorite":false,"rated":{"value":9.0},"watchlist":false}"#
let json2 = #"{"id":550,"favorite":false,"rated":false,"watchlist":false}"#
do {
let accountState1 = try JSONDecoder().decode(AccountState.self, from: Data(json1.utf8))
print(accountState1.rated ?? "nil") // "9.0\n"
let accountState2 = try JSONDecoder().decode(AccountState.self, from: Data(json2.utf8))
print(accountState2.rated ?? "nil") // "nil\n"
let encoded1 = try JSONEncoder().encode(accountState1)
print(String(data: encoded1, encoding: .utf8) ?? "nil")
let encoded2 = try JSONEncoder().encode(accountState2)
print(String(data: encoded2, encoding: .utf8) ?? "nil")
} catch {
print(error)
}

This would print:

9.0

nil

{"watchlist":false,"id":550,"favorite":false,"rated":{"value":9}}

{"watchlist":false,"id":550,"favorite":false,"rated":false}

Convert received Int to Bool decoding JSON using Codable

My suggestion: don't fight the JSON. Get it into a Swift value as quickly and with little fuss as possible, then do your manipulation there.

You can define a private internal structure to hold the decoded data, like this:

struct JSONModelSettings {
let patientID : String
let therapistID : String
var isEnabled : Bool
}

extension JSONModelSettings: Decodable {
// This struct stays very close to the JSON model, to the point
// of using snake_case for its properties. Since it's private,
// outside code cannot access it (and no need to either)
private struct JSONSettings: Decodable {
var patient_id: String
var therapist_id: String
var enabled: String
}

private enum CodingKeys: String, CodingKey {
case settings
}

init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let settings = try container.decode(JSONSettings.self, forKey: .settings)
patientID = settings.patient_id
therapistID = settings.therapist_id
isEnabled = settings.enabled == "1"
}
}

Other JSON mapping frameworks, such as ObjectMapper allows you to attach a transform function to the encoding/decoding process. It looks like Codable has no equivalence for now.



Related Topics



Leave a reply



Submit