Skip to content
This repository was archived by the owner on Sep 4, 2025. It is now read-only.
This repository was archived by the owner on Sep 4, 2025. It is now read-only.

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

Open

Description

@nstratos

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?

Activity

  1. aren55555 commented on Jul 6, 2017

    @aren55555
    Contributor

    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.

  2. svperfecta commented on Aug 31, 2017

    @svperfecta

    See also #110 :)

  3. SelfDrivingCarp commented on May 30, 2018

    @SelfDrivingCarp

    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
    }
    
  4. tisba commented on Aug 7, 2018

    @tisba

    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.

  5. AndreasBackx commented on Oct 10, 2018

    @AndreasBackx

    @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.

  6. adamlc commented on Jan 30, 2019

    @adamlc

    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!

  7. gadelkareem commented on May 9, 2020

    @gadelkareem

    Any updates on this?

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

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions