I'm getting the following error when trying to follow this tutorial:
Oh no something went wrong: A response for the QueryOn<Thing> did return successfully, but a serious error occurred when decoding the array of Thing.
Double check that you are passing Thing.self, and references to all other EntryDecodable classes into the Client initializer.
when using the following code to call contentful:
func fetch() {
let query = QueryOn<Thing>.where(field: .description, .exists(true))
client.fetchArray(of: Thing.self, matching: query) { (result: Result<ArrayResponse<Thing>>) in
switch result {
case .success(let things):
guard let firstThing = things.items.first else { return }
print(firstThing)
case .error(let error):
print("Oh no something went wrong: \(error)")
}
}
}
My Thing model is set as so:
and i currently have two Things added:
My Thing class looks like so:
final class Thing: EntryDecodable, FieldKeysQueryable {
enum FieldKeys: String, CodingKey {
case name, description
}
static let contentTypeId: String = "thing"
let id: String
let localeCode: String?
let updatedAt: Date?
let createdAt: Date?
let name: String
let description: String
public required init(from decoder: Decoder) throws {
let sys = try decoder.sys()
id = sys.id
localeCode = sys.locale
updatedAt = sys.updatedAt
createdAt = sys.createdAt
let fields = try decoder.contentfulFieldsContainer(keyedBy: Thing.FieldKeys.self)
self.name = try! fields.decodeIfPresent(String.self, forKey: .name)!
self.description = try! fields.decodeIfPresent(String.self, forKey: .description)!
}
}
Can anyone see what im missing?
so Contentful's documentation is all over the place. I had the same issue, but I managed to solve it after checking their documentation in their GitHub repo itself.
Basically you need to pass all Swift classes that conform the 'EntryDecodable' and 'FieldKeysQueryable' inside the Client initializer method.
Hope it helps!
Just wanted to make it easier for people visiting. All you need to do is ensure you've passed in your contentTypeClasses into the client Init method.
https://github.com/contentful/contentful.swift#map-contentful-entries-to-swift-classes-via-entrydecodable
fileprivate let client = Client(spaceId: spaceKey, accessToken: contentDeliveryKey, contentTypeClasses: [YourCustomClass.self])
final class YourCustomClass: EntryDecodable, FieldKeysQueryable {
static let contentTypeId: ContentTypeId = "yourCustomContentfulType"
public required init(from decoder: Decoder) throws {
let sys = try decoder.sys()
id = sys.id
localeCode = sys.locale
updatedAt = sys.updatedAt
createdAt = sys.createdAt
}
Thanks #Wazza for sharing the link to contentful's github docs.
Related
I've reworked this question after further research and in response to comments that it was too long.
I am downloading and decoding data, in CSV format using CodableCSV, from three URLs and I've been able to confirm that I am receiving all the data I expect (as of today, 35027 lines). As the data is decoded, I am injecting a NSManagedObjectContext in to the decoded object. Here is my managed object class:
import Foundation
import CoreData
#objc(MacListEntry)
class MacListEntry: NSManagedObject, Decodable {
//var id = UUID()
#NSManaged var registry: String?
#NSManaged var assignment: String?
#NSManaged var org_name: String?
#NSManaged var org_addr: String?
required convenience init(from decoder: Decoder) throws {
guard let keyManObjContext = CodingUserInfoKey.managedObjectContext,
let context = decoder.userInfo[keyManObjContext] as? NSManagedObjectContext,
let entity = NSEntityDescription.entity(forEntityName: "MacListEntry", in: context) else {
fatalError("Failed to receive managed object context")
}
self.init(entity: entity, insertInto: context)
let container = try decoder.container(keyedBy: CodingKeys.self)
self.registry = try container.decode(String.self, forKey: .registry)
self.assignment = try container.decode(String.self, forKey: .assignment)
self.org_name = try container.decode(String.self, forKey: .org_name)
self.org_addr = try container.decode(String.self, forKey: .org_addr)
}
private enum CodingKeys: Int, CodingKey {
case registry = 0
case assignment = 1
case org_name = 2
case org_addr = 3
}
}
public extension CodingUserInfoKey {
static let managedObjectContext = CodingUserInfoKey(rawValue: "managedObjectContext")
}
I then attempt to save the context using try context.save() but before doing so, examine the numbers of records I am trying to insert using:
print("Deleted objects: (self.persistentContainer.viewContext.deletedObjects.count)")
print("Inserted objects: (self.persistentContainer.viewContext.insertedObjects.count)")
print("Has changes: \(self.persistentContainer.viewContext.hasChanges)")
and get a different number of inserted records every time the code runs - always short, by around 0.5%. I am struggling to understand under what circumstances objects added to a managed object context in this way simply don't appear in the list of inserted objects and don't make it in to the saved database. Is there a practical limit on the number of records inserted in one go?
Can anyone suggest where else I should be looking - the error is tiny enough that it looks like the program is running fine, but it isn't.
Many thanks.
I think I've found the problem and if I'm correct, posting my solution here may help others. I'm downloading the three CSV files using Combine and dataTaskPublisher - creating three separate publishers and then merging them in to a single stream. While I was aware that I couldn't update the UI on a background thread so had added the .receive(on: DispatchQueue.main) in the chain (indicated by (1) in the code below), I put it AFTER the .tryMap { } where the decoding was happening. Because it was the process of decoding that inserted the object in to the managed object context, this must have been happening asynchronously and hence causing problems. By moving the .receive(on: ...) line ABOVE the .tryMap (see (2) below), this appears to have resolved the problem - or at least made the fetch, decode and insert return the correct number of inserted records consistently.
enum RequestError: Error {
case sessionError(error: HTTPURLResponse)
}
struct Agent {
let decoder: CSVDecoder
struct Response<T> {
let value: T
let response: URLResponse
}
func run<T: Decodable>(_ request: URLRequest) -> AnyPublisher<Response<T>, Error> {
print("In run()")
return URLSession.shared
.dataTaskPublisher(for: request)
.receive(on: DispatchQueue.main) // -- (1)
.tryMap { result -> Response<T> in
print(result)
guard let httpResponse = result.response as? HTTPURLResponse, httpResponse.statusCode == 200 else {
throw RequestError.sessionError(error: result.response as! HTTPURLResponse)
}
let value = try self.decoder.decode(T.self, from: result.data)
return Response(value: value, response: result.response)
}
//.receive(on: DispatchQueue.main). // -- (2)
.eraseToAnyPublisher()
}
}
struct Endpoint {
var agent: Agent
let base: String
}
extension Endpoint {
func run<T: Decodable>(_ request: URLRequest) -> AnyPublisher<T, Error> {
return agent.run(request)
.map(\.value)
.eraseToAnyPublisher()
}
func fetch(thing: String) -> AnyPublisher<[MacListEntry], Error> {
return run(URLRequest(url: URL(string: base+thing)!))
}
}
struct Response: Codable {
let name: String
}
--- snip ---
var requests:[AnyPublisher<[MacListEntry],Error>] = []
requests.append(endpoint.fetch(thing: "file1.csv"))
requests.append(endpoint.fetch(thing: "file2.csv"))
requests.append(endpoint.fetch(thing: "file3.csv"))
let _ = Publishers.MergeMany(requests)
.sink(receiveCompletion: { completion in
...
)
.store(in: &bag)
Attempting to refactor some legacy JSON parsing code to use Codable and attempting to reuse the existing Swift structs, for simplicity pls consider the following JSON:
{
"dateOfBirth":"2016-05-19"
...
"discountOffer":[
{
"discountName":"Discount1"
...
},
{
"discountName":"Discount2"
...
}
]
}
In the legacy code, the Swift struct Discount has a property 'discountType' whose value is computed based on Member struct's 'dateOfBirth' which is obtained from the JSON, question is, how do I pass the Member's dateOfBirth down to the each Discount struct? Or is there a way for structs lower in the hierarchy to access structs higherup in the hierarchy?
struct Member: Codable {
var dateOfBirth: Date?
var discounts: [Discount]?
}
struct Discount: Codable {
var discountName: String?
var memberDateOfBirth: Date? // *** Need to get it from Member but how?
var discountType: String? // *** Will be determined by Member's dateOfBirth
public init(from decoder: Decoder) throws {
// self.memberDateOfBirth = // *** How to set from Member's dateOfBirth?????
// use self.memberDateOfBirth to determine discountType
...
}
}
I am not able to use the decoder's userInfo as its a get property. I thought of setting the dateOfBirth as a static variable somewhere but sounds like a kludge.
Would appreciate any help. Thanks.
You should handle this in Member, not Discount, because every Codable type must be able to be decoded independently.
First, add this to Discount so that only the name is decoded:
enum CodingKeys : CodingKey {
case discountName
}
Then implement custom decoding in Member:
enum CodingKeys: CodingKey {
case dateOfBirth, discounts
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
dateOfBirth = try container.decode(Date.self, forKey: .dateOfBirth)
discounts = try container.decode([Discount].self, forKey: .discounts)
for i in 0..<discounts!.count {
discounts![i].memberDateOfBirth = dateOfBirth
}
}
The for loop at the end is where we give values to the discounts.
Going back to Discount, you can either make discountType a computed property that depends on memberDateOfBirth, or add a didSet observer to memberDateOfBirth, where you set discountType.
var discountType: String? {
if let dob = memberDateOfBirth {
if dob < Date(timeIntervalSince1970: 0) {
return "Type 1"
}
}
return "Type 2"
}
// or
var memberDateOfBirth: Date? {
didSet {
if let dob = memberDateOfBirth {
if dob < Date(timeIntervalSince1970: 0) {
discountType = "Type 1"
}
}
discountType = "Type 2"
}
}
You can access them like this
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
self.memberDateOfBirth = try values.decode(T.self, forKey: .result) //and whatever you want to do
serverErrors = try values.decode([ServerError]?.self, forKey: .serverErrors)
}
you can try in this way:
let container = try decoder.container(keyedBy: CodingKeys.self)
let dateString = try container.decode(String.self, forKey: .memberDateOfBirth)
let formatter = "Your Date Formatter"
if let date = formatter.date(from: dateString) {
memberDateOfBirth = date
}
if you want to know more check this approach:
https://useyourloaf.com/blog/swift-codable-with-custom-dates/
For Dateformatter you can check :
Date Format in Swift
https://nsdateformatter.com
I have a service whose response vary based on the orderNumer(input parameter which I pass to service). all most all object are same but only one object(ex meta object which is dictionary)vary based on order number. But I would like to reuse the same model class everywhere but due to different meta object can't be able to create that meta object in model class.I can achieve it by creating individual model class but not a right solution.
struct BookingInfo: Codable {
let created_time: Int
// Some other 20 key value pairs
let oms_meta: Meta /* oms_meta": {
"package": {
"description": "gzhdjjdjd"
}*/
}
// Meta for Order1
struct Meta: Codable {
let package: Description
enum CodingKeys : String, CodingKey {
case package = "package"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
package = try values.decode(Description.self, forKey: .package)
}
}
// Meta for Order 2
struct Meta: Codable {
let customer_payment_details: Int
let items: Int // this can be anything dictinary or some time array of dictionary
enum CodingKeys : String, CodingKey {
case package = "customer_payment_details"
case items = "items"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
customer_payment_details = try values.decode(Int.self, forKey: .package)
items = try values.decode(Int.self, forKey: .package)
}
}
Only meta parameters are varying from service to service.
Thanks in advance
If you want to only use 1 model, you should provide a variable for all the possible keys in the dictionary and set them as optional.
Let's say all orders have an orderNumber, but only some have an orderImage you could do it like this:
struct Order: Codable {
var orderNumber: Int
var orderImage: Data?
var orderName: String?
var orderDescription: String?
}
This way, the returned data only has to contain an orderNumber to be able to decode it.
Edit: You'd have to make a seperate model for each type of 'meta data' as they seem to be very different from eachother. Note that there is nothing wrong with creating a lot of model classes as long as they represent a specific data object.
[
{
"created_time": 1,
"oms_meta": {
"name": "1"
}
},
{
"created_time": 1,
"oms_meta": [
1
]
}
]
Suppose we have a response something like this, where you can have multiple oms_meta objects. So what we can do here is to specify an enum which will contain both array as well as dictionary, it doesn't matter if the service sends you array or dictionary if you need to specify other data types mentioned it in that enum. So it will be like,
struct BookingInfoElement: Codable {
let createdTime: Int?
let omsMeta: OmsMetaUnion?
enum CodingKeys: String, CodingKey {
case createdTime = "created_time"
case omsMeta = "oms_meta"
}
}
enum OmsMetaUnion: Codable {
case integerArray([Int])
case omsMetaClass(OmsMetaClass)
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if let x = try? container.decode([Int].self) {
self = .integerArray(x)
return
}
if let x = try? container.decode(OmsMetaClass.self) {
self = .omsMetaClass(x)
return
}
throw DecodingError.typeMismatch(OmsMetaUnion.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Wrong type for OmsMetaUnion"))
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
switch self {
case .integerArray(let x):
try container.encode(x)
case .omsMetaClass(let x):
try container.encode(x)
}
}
}
struct OmsMetaClass: Codable {
let name: String?
enum CodingKeys: String, CodingKey {
case name = "name"
}
}
typealias BookingInfo = [BookingInfoElement]
So, if you specifically want to have multiple types for a single variable you can create another enum with that multiple types and assign accordingly. You can access that variable using switch like, and check on array and dictionary types.
After doing some analyses I found the solution for my requirement. As I wanted to reuse the Model class and OMS_Meta dictionary different from product to product, I'm adding omsMeta to JSONDecoder().userInfo property
struct BookingInfo: Codable {
let created_time: Int
let oms_meta: [String: Any]
}
public static let metaUserInfo = CodingUserInfoKey(rawValue: "oms_meta")!
decoder.userInfo[BookingInfo.metaUserInfo] = jsonDecode
By doing this I can re use my model class.
I'm trying to encode and decode data from an API that represent an object as an array of strings, for instance:
[
["username", "message", "date"],
["username", "message", "date"],
["username", "message", "date"]
]
This is the corresponding Codable struct:
struct Message: Codable {
let user: String
let content: String
let date: String
private enum CodingKeys: Int, CodingKey {
case user = 0
case content = 1
case date = 2
}
}
Neither encoding or decoding work; encoding shows that a JSON object is created instead of an array:
let msg = Message(user: "foo", content: "content", date: "2019-06-04")
let jsonData = try! JSONEncoder().encode(msg)
let jsonString = String(data: jsonData, encoding: .utf8)!
The final string is:
{"content":"content","user":"foo","date":"2019-06-04"}
My goal is to obtain the following string
["foo", "content", "2019-06-04"]
Using a custom encode/decode method in the struct solves this, but forces to create a lot of boilerplate for each struct/class.
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
let values = try container.decode([String].self)
user = values[CodingKeys.user.rawValue]
content = values[CodingKeys.content.rawValue]
date = values[CodingKeys.date.rawValue]
}
How would one proceed to support this for any object?
And yes, this is a weird API, but this is not the first time I encounter one of those and user a different API format is not what I'm looking for here.
Rather than singleValueContainer use unkeyedContainer, it's more robust. If you want to assign the array items to struct members you have to write a custom initializer anyway
struct Message: Codable {
let user: String
let content: String
let date: String
init(from decoder: Decoder) throws {
var arrayContainer = try decoder.unkeyedContainer()
guard arrayContainer.count == 3 else { throw DecodingError.dataCorruptedError(in: arrayContainer, debugDescription: "The array must contain three items") }
user = try arrayContainer.decode(String.self)
content = try arrayContainer.decode(String.self)
date = try arrayContainer.decode(String.self)
}
func encode(to encoder: Encoder) throws {
var arrayContainer = encoder.unkeyedContainer()
try arrayContainer.encode(contentsOf: [user, content, date])
}
}
Our json-over-rest(ish) API follows a pattern of encoding URLs accessible from a particular object in a list format, under the #links key. Made-up example:
{ "id": "whatever",
"height_at_birth": 38,
"#links": [
{ "name": "shield-activation-level",
"url": "https://example.com/some/other/path" },
{ "name": "register-genre-preference",
"url": "https://example.com/some/path" }
]
}
On the Swift side we use phantom types and optionality for type-safety. For instance the above json might correspond to a struct like:
struct Baby {
let id: String
let heightAtBirth: Int
let registerGenrePreference: Link<POST<GenrePreference>>
let shieldActivationLevel: Link<GET<PowerLevel>>?
let magicPowers: Link<GET<[MagicPower]>>?
}
The phantom types ensure that we can't post a feeding schedule to the registerGenrePreference URL by accident, and the optionality indicates that a well-formed Baby-json will always contain an #links entry for registerGenrePreference but that the other two links might or might not be present. So far so good.
I would like to use Decodable to consume this json format, ideally with a minimum of init(decoder:Decoder) custom implementations. But I am stumped by the #links entries.
I think I see what this would look like if I do the entire decoding by hand:
get the baby's container,
from it get a nested unkeyed container for the #links key
iterate over its values (which should be [String:String] dicts) and build a dict matching names to URLs
for each link Baby expects, look it up in the dict (and throw if the property was non-optional and the link is missing)
But steps 2 and 3 are the same for every class following this pattern (not ideal) and even worse, having to do this also prevents me from using the compiler-provided Decodable implementation so I also have to manually decode all the other properties of Baby.
If it helps I'm perfectly happy restructuring Baby; one obvious step that might help would be:
struct Baby {
let id: String
let heightAtBirth: Int
let links: Links
struct Links {
let registerGenrePreference: Link<POST<GenrePreference>>
let shieldActivationLevel: Link<GET<PowerLevel>>?
let magicPowers: Link<GET<[MagicPower]>>?
}
}
And of course I expect we'll have to add coding keys, even if only for the snake/camel-case conversion and the #:
enum CodingKeys: String, CodingKey {
case id
case heightAtBirth = "height_at_birth"
case links = "#links"
}
I could probably make a manual Decodable conformance for Baby.Links, following the pattern above, but that still will mean repeating the "get the unkeyed collection, transform it to a dict, look up coding-keys in the dict" steps once for each class following this pattern.
Is there a way to centralise this logic?
You actually have a well defined structure for your links. They are a dictionary [String : String] so you can use that to your advantage in using Decodable.
You may want to consider setting up your structs like below. The links are decoded from the JSON, and an extension provides your optionality of the exact links you are looking for.
A Linkable protocol could be used to add the conformance to any class/struct that needs it.
import Foundation
struct Link: Decodable {
let name: String
let url: String
}
protocol Linkable {
var links: [Link] { get }
}
extension Linkable {
func url(forName name: String) -> URL? {
guard let path = links.first(where: { $0.name == name })?.url else {
return nil
}
return URL(string: path)
}
}
struct Baby: Decodable, Linkable {
let id: String
let heightAtBirth: Int
let links: [Link]
enum CodingKeys: String, CodingKey {
case id = "id"
case heightAtBirth = "height_at_birth"
case links = "#links"
}
static func makeBaby(json: String) throws -> Baby {
guard let data = json.data(using: .utf8) else {
throw CocoaError.error(.fileReadUnknown)
}
return try JSONDecoder().decode(Baby.self, from: data)
}
}
extension Baby {
var registerGenrePreference: URL? {
return url(forName: "register-genre-preference")
}
var shieldActivationLevel: URL? {
return url(forName: "shield-activation-level")
}
}
let baby = try Baby.makeBaby(json: json)
baby.registerGenrePreference
baby.shieldActivationLevel
The following pattern gives me complete type-safety, and most of the implementation lives in the one-off utility class UntypedLinks. Each model class needs to define a nested class Links with a custom decode(from: Decoder), but the implementation of those is completely boilerplate (probably can be automated with simple code-generation tooling) and reasonably readable.
public struct Baby: Decodable {
public let id: String
public let heightAtBirth: Int
public let links: Links
enum CodingKeys: String, CodingKey {
case id
case heightAtBirth = "height_at_birth"
case links = "#links"
}
public struct Links: Decodable {
let registerGenrePreference: Link<POST<GenrePreference>>
let shieldActivationLevel: Link<GET<PowerLevel>>?
let magicPowers: Link<GET<[MagicPower]>>?
enum CodingKeys: String, CodingKey {
case registerGenrePreference = "register-genre-preference"
case shieldActivationLevel = "shield-activation-level"
case magicPowers = "magic-powers"
}
public init(from decoder: Decoder) throws {
let links = try UntypedLinks<CodingKeys>(from: decoder)
registerGenrePreference = try links.required(.registerGenrePreference)
shieldActivationLevel = links.optional(.shieldActivationLevel)
magicPowers = links.optional(.magicPowers)
}
}
}
public class UntypedLinks<CodingKeys> where CodingKeys: CodingKey {
let links: [String: String]
let codingPath: [CodingKey]
class UntypedLink: Codable {
let name: String
let url: String
}
public init(from decoder: Decoder) throws {
var container = try decoder.unkeyedContainer()
var links: [String: String] = [:]
while !container.isAtEnd {
let link = try container.decode(UntypedLink.self)
links[link.name] = link.url
}
self.links = links
self.codingPath = container.codingPath
}
func optional<Phantom>(_ name: CodingKeys) -> Link<Phantom>? {
return links[name.stringValue].map(Link.init)
}
func required<Phantom>(_ name: CodingKeys) throws -> Link<Phantom> {
guard let link: Link<Phantom> = optional(name) else {
throw DecodingError.keyNotFound(
name,
DecodingError.Context(
codingPath: codingPath,
debugDescription: "Link not found")
)
}
return link
}
}