mattpolzin / JSONAPI

Swift Codable JSON:API framework
MIT License
75 stars 19 forks source link

Encoding Includes #52

Closed seifscape closed 4 years ago

seifscape commented 4 years ago

Hello I am trying to figure out how to use includes, I been following your examples. But when it comes to a document with includes, I keep getting Response could not be decoded because of error: The operation couldn’t be completed. (JSONAPI.JSONAPIEncodingError error 0.)

mattpolzin commented 4 years ago

What versions of this JSONAPI library and Swift are you using?

That error is not giving us as much information as a JSONAPIEncodingError is capable of giving us. Are you getting this error from a statement following a try! by any chance? If you are, you can switch to a try/catch and either print the error out in the catch (I think this tends to give a more verbose printout) or explicitly handle the JSONAPIEncodingError.

seifscape commented 4 years ago

I am using the latest on 3.0.0 alpha3. I will try that out, and I also have a question about how I would return a Decodable document so I can access both primary and includes. I currently have it working on primary, but I cannot access the includes.

mattpolzin commented 4 years ago

If we can get more detailed information about the error than "The operation couldn’t be completed. (JSONAPI.JSONAPIEncodingError error 0.)" I might be able to help, but another thing that would help me understand what you're looking at would be the types you are using. You don't need to share anything proprietary, just boil down some details and send me your ResourceObject and Document types, something like the following:

enum MyDescription: JSONAPI.ResourceObjectDescription {
  let jsonType: String = "type_name"
  struct Attributes: JSONAPI.Attributes {
    let name: Attribute<String>
    let age: Attribute<Int?>
  },
  struct Relationships: JSONAPI.Relationships {
    let friend: ToOneRelationship<MyType, NoMetadata, NoLinks>
  }
}
typealias MyType = JSONAPI.ResourceObject<MyDescription, NoMetadata, NoLinks, String>
typealias MyDocument = JSONAPI.Document<SingleResourceBody<MyType>, NoMetadata, NoLinks, Include1<MyType>, NoAPIDescription, BasicJSONAPIError<String>>

Any further pseudo-code representative of your encoding/decoding code would be helpful as well.

seifscape commented 4 years ago

`// public protocol JsonApiModel: ResourceObjectDescription{} struct SessionDescription: JsonApiModel { static var jsonType: String = "sessions"

struct Attributes: JsonApiAttributes {
    var createdAt: TransformedAttribute<String?, ISODateTransformer>?
    var updatedAt: TransformedAttribute<String?, ISODateTransformer>?
    var email: Attribute<String?>?
    var password: Attribute<String?>?
}

struct Relationships: JSONAPI.Relationships {
    var devices: ToOneRelationship<Device, NoMetadata, NoLinks>?
}

} typealias Session = Resource

extension Session { init(id: Session.Id? = nil) { self = Session(id: id ?? Session.Id(rawValue: ""), attributes: Session.Attributes(), relationships: Session.Relationships(), meta: .none, links: .none) } }`

I was hoping to return this and have access to both. typealias SessionDocumentDeviceInclude = Document<SingleResourceBody<Session>,Include1<Device>>

I am using Alamofire 5 and I am getting the right response when printing out, but it keeps failing when I am trying to decode with SessionDocumentDeviceInclude.Self type

mattpolzin commented 4 years ago

Any update on the error message (printing it out to get a bit more info)? I am wondering if “JSONAPIEncodingError error 0” is the . typeMismatch case since that is the first case or not and I hope that my idea for printing the error directly (or maybe even printing String(describing: error)) will confirm that and give us the additional info we need to determine where things are going wrong.

What’s an example of the response body being parsed by your SessionDocumentDeviceInclude?

mattpolzin commented 4 years ago

Another thing that might help pin down the issue is the newest alpha release. 3.0.0-alpha.4 includes breaking changes for any existing error handling that targets particular DecodingErrors but the upshot is that default error handling should often be much more descriptive. I think in your case printing the errors from 3.0.0-alpha.4 might get us an answer much more quickly than before.

seifscape commented 4 years ago

I tried updating to 3.0.0-alpha.4, but in the includes.swift I am getting this error 'error' is inaccessible due to 'internal' protection level

SessionDescription sample response should be: { "data": { "id": "491", "type": "mobile_users", "attributes": { "uuid": "adff16e7-9133-4ec7-b969-e0d25c63c022", "account_name": "dummy_480", "email": "user816@fake.com", "created_at": "2018-11-02T20:43:34Z", "updated_at": "2018-11-02T20:43:34Z", "invitation_sent_at": null, "invitation_accepted_at": null, "disabled": false, "disabled_at": null, "password_expired_at": "2019-01-31T20:43:34Z" }, "relationships": { "devices": { "data": [ { "id": "255", "type": "devices" } ] }, "user_role": { "data": { "id": "387", "type": "user_roles" } }, "profile": { "data": { "id": "842", "type": "profiles" } }, "language": { "data": { "id": "1", "type": "languages" } }, } }, "included": [ { "id": "255", "type": "devices", "attributes": { "udid": "hleh46h4g0g6gago7rce", "technology": "ios", "access_token": "425d80493f7350191317265d47fea867341949b8e7d566370892725e0571718d", "push_id": "sbe7ykfhm81zxlnrxv4r1aa050zozcvxqameubpbe3sb79p0cllkwj6zjfrl0xfo", "created_at": "2018-11-02T20:43:34Z", "updated_at": "2018-11-02T20:43:34Z", "app_version": null } } ] }

mattpolzin commented 4 years ago

I tried updating to 3.0.0-alpha.4, but in the includes.swift I am getting this error 'error' is inaccessible due to 'internal' protection level

This would happen if the version of Poly you are building against was not updated to 2.3.1 when you updated to the latest alpha of JSONAPI. I am guessing if you look at Package.resolved you will see Poly is still at version 2.3.0. Not sure why, but if you swift package update it should resolve Poly to 2.3.1. If you are using an Xcode project, you will also need to re-run swift package generate-xcodeproj in order for Xcode to pick up the new Poly version (and be sure to do a clean & build).

mattpolzin commented 4 years ago

Regarding your example response, I am not sure how it happened but that is not valid JSON as-is. I was able to make it valid by deleting the , } at the end of the relationships (column 661-664) and adding a } at the very end of everything. Was this possibly a copy-paste error or could it be why decoding is failing?

seifscape commented 4 years ago

I am using cocoapods, and I can switch over to SwiftPM. You are correct, it was my mistake on the copy and paste.

mattpolzin commented 4 years ago

probably no need to switch to SwiftPM, but I am not as familiar with CocoaPods or why it might not be grabbing the latest Poly for you. One way or the other, make sure you've got Poly 2.3.1 and you should be past the aforementioned Swift build error.

mattpolzin commented 4 years ago

You pasted your SessionDescription but it sounds like you are able to get a Session to decode successfully and you run into trouble when you add a Device include so do you mind also sharing your DeviceDescription with me?

seifscape commented 4 years ago

So the error I am getting now is Include 2 failed to parse: found JSON:API type "user_roles" but expected "devices" Response could not be decoded because of error:

Here is DeviceDescription:


struct DeviceDescription: JsonApiModel, HasDelete, HasSave {
    static var jsonType: String = "devices"
    struct Attributes: JsonApiAttributes {
        var createdAt: TransformedAttribute<String?, ISODateTransformer>?
        var updatedAt: TransformedAttribute<String?, ISODateTransformer>?
        var udid: Attribute<String?>?
        var technology: Attribute<String?>?
        var accessToken: Attribute<String?>?
        var pushId: Attribute<String?>?
        var appVersion: Attribute<String?>?
    }
    typealias Relationships = NoRelationships
}
typealias Device = Resource<DeviceDescription>

extension Device {
    init(id: Device.Id? = nil) {
        self = Device(id: id ?? Device.Id(rawValue: ""), attributes: Device.Attributes(), relationships: .none, meta: .none, links: .none)
    }
}
mattpolzin commented 4 years ago

Ah, yeah, so as of the current version of this library, an include of an unknown type will cause failure to decode. In some cases this is desirable because it could indicate a serious problem in either the server or client code that an unexpected resource is included in a body. In this case, maybe that is less useful to you. You have two options:

  1. Tell the API you only want devices included since that is all that you expect to decode
  2. Add ResourceObjects for the other types you expect in the response and use (for example) Include2<Device, UserRole>.
mattpolzin commented 4 years ago

I would like in the future to allow you to say "decode this response and if you find unexpected includes then just continue" but right now the only option is failure.

seifscape commented 4 years ago

That was it! I actually got it to work, I thought that I didn't need to include the other Includes types and it worked! I wanted to thank you for your help with the error output and update that you worked on!