mattpolzin / JSONAPI

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

Relationships + Includes mapping #101

Closed lukewakeford closed 1 year ago

lukewakeford commented 2 years ago

Hey, I'm using this framework to add support for a new JSON:API endpoint/feature to an existing project with many other features that use Codable models with HAL+JSON format. so far so good, really appreciate the hard work put in to this framework 🙌🏻 it's enabled me to slot the new feature in and re-use our existing Codable networking setup which is awesome!.

Similarly to #43 I've got so far implementing a batch document and was looking for the simplest way to access relationships/includes. (I'm used to doing this with dot syntax since we've been using HAL+JSON up to this point, where relationships are embedded in to primary resources giving direct access to those full objects) For now with JSONAPI it looks like the best way to do this is to filter or look up includes by id?

In the same vein after implementing includes I couldn't help but wonder why we have to define the number of includes BatchDocument<Person, Include2<Dog, Cat>> when these are already defined as JSONAPI.Relationships in our ResourceObjectDescription

enum PersonDescription: ResourceObjectDescription {
    static var jsonType: String = "person"
    struct Attributes: JSONAPI.Attributes {
        let name: Attribute<String>
    }
    struct Relationships: JSONAPI.Relationships {
        let dogs: ToManyRelationship<Dog, NoIdMetadata, NoMetadata, NoLinks>
        let cats: ToManyRelationship<Cat, NoIdMetadata, NoMetadata, NoLinks>
    }
}
typealias Person = Resource<PersonDescription>

Thanks again and in advance for any knowledge here. ✌🏻

mattpolzin commented 2 years ago

For now with JSONAPI it looks like the best way to do this is to filter or look up includes by id

This is a reasonably accurate statement. I'll rephrase: JSONAPI does not offer a built-in way to embed included relationships on the primary resource(s). This is more about not being prescriptive than anything else.

If your project uses a store to cache all resources, you actually don't want related resources directly embedded on primary resources, you want to store everything in the cache and know you are looking up the latest versions of each thing from your store each time you need them. It's possible to use this kind of setup conveniently, but it may require some machinery built outside of JSONAPI (and that machinery wouldn't be one-size-fits all in that there are any number of ways to implement a cache of resources).

If your project instead wants to consider each JSON:API response as a complete picture of some resources, I agree that the JSONAPI library leaves something to be desired, but I didn't want to build the library to one use-case or the other (in fact, my use-case was the universal store model).

Have you had the chance to take a look at the JSONAPIResourceStore provided by https://github.com/mattpolzin/JSONAPI-ResourceStorage? This was my attempt at providing a starting place (that in some cases might be more than enough) for those wanting to take one or more JSONAPI documents and access resources (including relatives) as if they were one coherent chunk of data. It's not as well documented as the JSONAPI library, but I would be happy to talk through how it might be helpful to you.

mattpolzin commented 2 years ago

I couldn't help but wonder why we have to define the number of includes BatchDocument<Person, Include2<Dog, Cat>> when these are already defined as JSONAPI.Relationships in our ResourceObjectDescription

It's really just to allow you to be incredibly specific about what is possible. A client can choose to express an API request's result as not offering includes of all the types available on the primary resource if the client is not requesting all of those includes from the server.

I find myself wanting to offer a typealias that simplifies the Document type in the case that the Includes and primary resource's Relationships should represent the same types of things, but I'm struggling to come up with a good solution. I think Swift's type-level programming might not be strong enough to make this task as easy as I'd like it to be.

mattpolzin commented 2 years ago

Regarding using JSONAPI-ResourceStorage, I think the convenience you are after is mostly available with the newest release (version 0.4.0). You can specifically take a glance at how the new example test digs into a primary resource and its relatives as if the relatives were directly embedded in the primary resource: https://github.com/mattpolzin/JSONAPI-ResourceStorage/blob/0.4.0/Tests/JSONAPIResourceStoreTests/SingleResponseExample.swift#L133..L143

lukewakeford commented 2 years ago

Thats great thanks for taking the time to reply 👍🏻

mattpolzin commented 1 year ago

I'm going to close this as answered, but anyone can feel free to follow up if a question in the same vein comes into focus!