Skip to content

Simplification of Unmarshal API & Access to Meta and Links #95

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
nstratos opened this issue Jun 29, 2017 · 7 comments
Open

Simplification of Unmarshal API & Access to Meta and Links #95

nstratos opened this issue Jun 29, 2017 · 7 comments

Comments

@nstratos
Copy link
Contributor

With the recent simplification of the Marshal API (cf83b97) I think it might be a good time to consider something similar for the Unmarshal case.

While doing so, it would be even better to provide a solution for accessing the Meta and Links of the JSON API document (relevant issues #82, #68 and #64).

Right now there are two core functions that handle Unmarshaling:

UnmarshalPayload(in io.Reader, model interface{}) error

UnmarshalManyPayload(in io.Reader, t reflect.Type) ([]interface{}, error)

As long as the model is a pointer to a struct or a pointer to a slice, these can be simplified to one function with the same exact signature as UnmarshalPayload(in io.Reader, model interface{}) error. I have already done a proof of concept (nstratos/go-kitsu@8b8706c) for a package I am writing and it seems to be working fine.

The name could be kept the same for compatibility while emphasizing in the docs the new functionality of providing a pointer to slice to fill with data (instead of using UnmarshalManyPayload) or it could be made to new a function that covers both cases. For this discussion I am going to name this new function UnmarshalAll but it could be anything.

A separate but related issue (because of the function signature) is giving access to Meta and Links. The simplest way would be to return something extra besides the error. Perhaps something like this:

type Extras struct {
    Links *Links
    Meta *Meta
}

// Expecting v to be pointer to struct or pointer to slice.
func UnmarshalAll(r io.Reader, v interface{}) (Extras, error)

Thus with this function, the Unmarshal API gets simplified and access to Meta and LInks is provided as well.

Thoughts?

@aren55555
Copy link
Contributor

aren55555 commented Jul 6, 2017

This extra workload is why I only bit off simplifying the Marshal API initially.

With that being said I have put a lot of thought into this. I had considered doing something similar to what you have suggested, however I could not work out how it would work out in a situation where like links are on the top level data collection (self/next/last) and also on each memeber of said collection (self). Example:

{
    "links": {
        "self": "http://example.com/articles",
        "next": "http://example.com/articles?page[offset]=2",
        "last": "http://example.com/articles?page[offset]=10"
    },
    "data": [{
        "type": "articles",
        "id": "1",
        "attributes": {
            "title": "JSON API paints my bikeshed!"
        },
        "links": {
            "self": "http://example.com/articles/1"
        }
    }]
}

I think a better solution is to have the generic Unmarshal look like:

func Unmarshal(r io.Reader, t interface{}) error

The t passed in would optionally implement a defined interface for handling Meta/Links etc.

In this way the same interface can be used for types representing a collection as well as with types that are members of the collection.

type Team struct {
	ID   int    `jsonapi:"primary,team"`
	Name string `jsonapi:"attr,name"`
}

type Teams []*Team

Both Team and Teams in the example above would implement an optional interface for handling Meta. Then given the following JSON API payload to Unmarshal:

{
    "data": [{
        "type": "team",
        "id": "1",
        "attributes": {
            "name": "Toronto Maple Leafs"
        },
        "meta": {
            "details": 19
        }
    }, {
        "type": "team",
        "id": "3",
        "attributes": {
            "name": "Toronto Raptors"
        },
        "meta": {
            "details": 15
        }
    }],
    "meta": {
        "details": "updated"
    }
}

Teams implementation of the interface would have to handle the part:

"meta": {
        "details": "updated"
}

and the Team implementation of the interface would have to handle the part:

"meta": {
    "details": 19
}

or

"meta": {
    "details": 15
}

I haven't yet stared on this - it's just the pattern that I have left to brew in the back of my head.

@svperfecta
Copy link

See also #110 :)

@SelfDrivingCarp
Copy link

I have the exact case that @aren55555 noted: a response with the data portion being a list of individual objects then a links object with those pagination fields.

Is there an ugly way to workaround this in lieu of an elegant solution? In my own code I've done this:

type PaginationLinks struct {
	Self  string `json:"self,omitempty"`
	First string `json:"first,omitempty"`
	Last  string `json:"last,omitempty"`
	Next  string `json:"next,omitempty"`
	Prev  string `json:"prev,omitempty"`
}

type Page struct {
	Links *PaginationLinks `json:"links,omitempty"`
	Data  interface{}      `json:"data,omitempty"`
}

func (p PaginationLinks) NextPage() string {
	return p.Next
}

@tisba
Copy link

tisba commented Aug 7, 2018

Hi there 👋 Am I understanding this discussion correctly, that currently there is no way to access meta when using UnmarshalPayload or UnmarshalManyPayload? #82 implies that this is still the case.

@AndreasBackx
Copy link

AndreasBackx commented Oct 10, 2018

@nstratos @aren55555 any information on the status of this and the project itself? Would love to be able to easily access links from responses as a client. For example being able to access the redirect field:

{
  "data": {
    "type": "accountInformationAccessRequest",
    "links": {
      "redirect": "https://callback.ibanity.com/sandbox/fi/aiar/i?abc=="
    },
    "id": "7471cd7b-2509-4451-8814-3e6a3959e72b",
    "attributes": {
      "requestedAccountReferences": [
        "BE6985304897158018"
      ]
    }
  }
}

Edit: this might be incorrect according to the links spec.

@adamlc
Copy link

adamlc commented Jan 30, 2019

This would also be really useful for me. What about if we had the meta embedded in the struct (same with links)? Using the teams example above:

{
    "data": [{
        "type": "team",
        "id": "1",
        "attributes": {
            "name": "Toronto Maple Leafs"
        },
        "meta": {
            "details": 19
        }
    }, {
        "type": "team",
        "id": "3",
        "attributes": {
            "name": "Toronto Raptors"
        },
        "meta": {
            "details": 15
        }
    }],
    "meta": {
        "details": "updated"
    }
}
type Team struct {
	ID   int    `jsonapi:"primary,team"`
	Name string `jsonapi:"attr,name"`
        Details int `jsonapi:"meta,details"`
}

type Teams []*Team

Obviously the above would only solve being able to access meta/links at a resource level and not the top level. I suppose for that the unmarshal function would need to return something for those.

Edit: I've had a go at the above for meta attributes, seems to work OK, see here. I can submit a PR if needs be!

@gadelkareem
Copy link

Any updates on this?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

8 participants