Codable and JSON:API spec?

codable

(Chris Anderson) #1

I'm beginning work with a server API that renders its' responses using the JSON:API (https://jsonapi.org/format/) syntax.

Is there any literature/documentation anywhere on using Codable with JSON:API? Been searching a lot (tough thing to Google for) but have not found anything.

It seems to me that the structure of Codable is not, at all, designed to work with such an object. (For example, coding into a specific object based on the type field, handling relationships with keys defining the class of the relationship).

But, as far as JSON:API is the closest thing to a JSON standard out there, Codable should handle it?

    {
      "data": [
        {
          "id": "104729",
          "type": "address",
          "attributes": {
            "id": 104729,
            "city": "Sunnydale",
            "lat": "33.860736",
            "lng": "-77.094892",
            "state": "VA",
            "street_1": "3627 Columbia Ave",
            "street_2": "",
            "zip": "22031"
          },
          "relationships": {
            "user": {
              "data": {
                "id": "1",
                "type": "user"
              }
            },
            "zone": {
              "data": {
                "id": "109",
                "type": "zone"
              }
            }
          }
        },
        {
          "id": "104535",
          "type": "address",
          "attributes": {
            "id": 104535,
            "city": "Sunnydale",
            "lat": "33.863043",
            "lng": "-77.096668",
            "state": "VA",
            "street_1": "809 S Hollywood St",
            "street_2": "",
            "zip": "22031"
          },
          "relationships": {
            "user": {
              "data": {
                "id": "1",
                "type": "user"
              }
            },
            "zone": {
              "data": {
                "id": "109",
                "type": "zone"
              }
            }
          }
        }
      ],
      "included": [
        {
          "id": "109",
          "type": "zone",
          "attributes": {
            "id": 109,
            "name": "Hellmouth",
            "active": true,
            "additional_services": [
              "delivery",
              "pickup",
            ]
          },
          "relationships": {
            "region": {
              "data": {
                "id": "3",
                "type": "region"
              }
            }
          }
        }
      ]
    }

(Jon Shier) #2

Codable is more about representations (translation) than schemas (connections between values). So while Codable may be able to handle your JSON decoding, it's not really designed for something like JSON:API. Personally I find JSON: API horribly overcomplicated for 90% of RESTful APIs, but I imagine you could combine Codable with a higher level framework to manage your schemas and get it to work.


(Mathew Polzin) #3

This is actually something I have spent a lot of time on as a personal project. As @Jon_Shier points out, Codable is not really intended to handle specification conformance (it's the Encoder you use that enforces a specification). In your case, the best-fit encoder only enforces JSON conformance and JSON API Spec is a much higher level specification. You point out how JSON:API's type cannot be naturally represented by a Codable type; that is the tip of the iceberg, actually, because Codable cannot enforce any of the structure the JSON:API meta-schema requires, either. If you want you can take a look at my crack at using Codable for JSON:API (https://github.com/mattpolzin/JSONAPI) to see how I went about it, but you aren't going to find any short paths to JSON:API compliance -- it's a really large (in my opinion well-thought-out) specification.


(Chris Anderson) #4

Yeah, I think JSON:API is overkill in most cases as well, but it's the format I have to work with so don't have a choice. I'm also less concerned with maintaining conformance and mostly with just, how do I ingest it in the first place (preferably using Codable, because it should be able to handle any kind of JSON) without a bajillion hoops to jump through.


(Mathew Polzin) #5

Well, the structure of any given response can be represented by decodable structs fairly readily; what you don’t get for free is the assurances that you would really like to have (like the JSON API type string matches what you expect for the struct you are trying to decode). If you want to post an example or PM me I can help you get something that DOES decode but DOES NOT offer much safety.

EDIT: I do see that you've got an example JSON API payload in your original post, but I don't want to assume you'd like help building out a structure for decoding it -- I think you might have that part down and just want to know if there's a convenient way to take things a step further.


(Dale Buckley) #6

We are using JSONAPI extensively in our project and actually share code between our backend service (built on Vapor) and iOS app. The issue with the type field isn't much of an issue if you approach structuring your model with generics.

For example; codable expects you to already know the type you want to decode, so whenever we want to decode a payload from an endpoint, we pass that type into the document struct and so it knows what to decode. We don't actually rely on the type field to tell us what the type is as we know what to expect before we get the payload. If the payload doesn't match then we don't know how to handle it anyway, so the decode fails and we pass the corresponding errors back up the stack.

So when we make a request we define the return type as Document<MyModel>. MyModel defines the resource we expect to be returned within the attributes property.

As for the 'included` resources; we fuzzy decode them into a recurring JSON enum (that contains basic JSON types) so we can parse the data but they aren't decoded to a specific object. If we then want to then decode that object we have a generic method that expects a type, that type is then used to attempt to decode the JSON type into that concrete type. This isn't actually the most performant operation in the world as we re-encode our JSON type back into raw JSON and then use that to decode into the type we expect, but it works flawlessly for our use cases and it's all asynchronous so any delay isn't noticeable from a users perspective.

I would love to share the code with you but it's currently owned by the company I work for and would need to be open sourced by them.

In short, Codable won't solve all your woes, it's not magic as it's supposed to be type safe which means expecting you to know whats being decoded. But on the other hand it's not impossible to work with either. Just approach the problem from a different direction and you should be able to find your way though it.


(Mathew Polzin) #7

This is the approach I took as well (using generics). If you’re ever interested in a refactor, you can push the success/failure of decoding specific “included” types into the decoding of your document by swapping your “fuzzy JSON enum” with a set of generic types. A bit hard to explain concisely but I linked to my code above so you can read about the idea under “Includes” in the readme.

I called my protocol Poly and there are generic types for Poly0..Poly9 right now. They are typealiased to NoIncludes and Include1..Include9 for readability but the idea is that if I expect to find two types of things included I would use Poly2<Thing1,Thing2>. The decoding of these Poly types just attempts each type it is specialized on and throws if it cannot decode any of them.


(Mathew Polzin) #8

I should call out, for those about to point it out anyway, that Poly2 is indeed isomorphic to Either.