Skip to content

Bring consistency to top-level and resource-level links #311

@dgeb

Description

@dgeb

I'd like to float a proposal that comes from considering points raised by @ahacking, @ethanresnick, @wvteijlingen, and others in #238, #262, #276, and other recent issues. It represents a breaking change that should be considered carefully before the spec hits 1.0.

The proposal is to make the format of the links object identical in both the top-level and resource-level. Each link can be represented either by an object or a string. Link objects can contain two properties: href and / or type. If a link is represented with a string, it should be assumed to be href.

The implications of this change are pretty drastic:

  • It reduces processing rules for implementations by establishing a single rule for processing links.
  • IDs for has-one and has-many relationships will be represented alongside other attributes in a resource. (Of course, an implementation may instead choose to exclusively provide hrefs for relationships and never provide related IDs.)
  • The term links will never appear in URL template expressions or in relationship URLs.
  • There is no need to call out a separate "collection object"
  • It will be clear that resource-level links are redundant and used to override top-level links. I imagine that many APIs will exclusively use top-level links and never use resource-level links.
  • It will be clear that expressions in URL templates can reference any attribute in a resource (even complex object attributes) in order to form links to another resource (see Cannot link from embedded objects #238).
  • It will move JSON API one step closer to "standard REST APIs", which will probably increase adoption.

If there is adequate support for this change, I'll create a PR for detailed review.

Examples

Mixed top-level and resource-level links

Before:

{
  "links": {
    "posts.comments": "http://example.com/comments?post={posts}"
  },
  "posts": [{
    "id": "1",
    "title": "Rails is Omakase",
    "links": {
      "comments": {
        "href": "http://example.com/comments?post=1"
      }
    }
  }, {
    "id": "2",
    "title": "The Parley Letter",
    "links": {
      "comments": {
        "href": "http://example.com/comments?post=2"
      }
    }
  }]
}

After:

{
  "links": {
    "posts.comments": "http://example.com/comments?post={posts}"
  },
  "posts": [{
    "id": "1",
    "title": "Rails is Omakase",
    "links": {
      "comments": "http://example.com/comments?post=1"
    }
  }, {
    "id": "2",
    "title": "The Parley Letter",
    "links": {
      "comments": "http://example.com/comments?post=2"
    }
  }]
}

Top-level links, resource-level relationship IDs

Before:

{
  "links": {
    "posts.author": {
      "href": "http://example.com/people/{posts.author}",
      "type": "people"
    },
    "posts.comments": {
      "href": "http://example.com/comments?post={posts}",
      "type": "comments"
    }
  },
  "posts": [{
    "id": "1",
    "title": "Rails is Omakase",
    "links": {
      "author": "9"
    }
  }, {
    "id": "2",
    "title": "The Parley Letter",
    "links": {
      "author": "9"
    }
  }]
}

After:

{
  "links": {
    "posts.author": {
      "href": "http://example.com/people/{posts.author}",
      "type": "people"
    },
    "posts.comments": {
      "href": "http://example.com/comments?post={posts}",
      "type": "comments"
    }
  },
  "posts": [{
    "id": "1",
    "title": "Rails is Omakase",
    "author": "9"
  }, {
    "id": "2",
    "title": "The Parley Letter",
    "author": "9"
  }]
}

Resource-level links

Before:

{
  "posts": [{
    "id": "1",
    "title": "Rails is Omakase",
    "links": {
      "author": {
        "href": "http://example.com/people/9"
      },
      "comments": {
        "href": "http://example.com/comments?post=1"
      }
    }
  }, {
    "id": "2",
    "title": "The Parley Letter",
    "links": {
      "author": {
        "href": "http://example.com/people/9"
      },
      "comments": {
        "href": "http://example.com/comments?post=2"
      }
    }
  }]
}

After:

{
  "posts": [{
    "id": "1",
    "title": "Rails is Omakase",
    "links": {
      "author": "http://example.com/people/9",
      "comments": "http://example.com/comments?post=1"
    }
  }, {
    "id": "2",
    "title": "The Parley Letter",
    "links": {
      "author": "http://example.com/people/9",
      "comments": "http://example.com/comments?post=2"
    }
  }]
}

Compound documents

Before:

{
  "links": {
    "posts.author": {
      "href": "http://example.com/people/{posts.author}",
      "type": "people"
    },
    "posts.comments": {
      "href": "http://example.com/comments/{posts.comments}",
      "type": "comments"
    }
  },
  "posts": [{
    "id": "1",
    "title": "Rails is Omakase",
    "links": {
      "author": "9",
      "comments": [ "1", "2", "3" ]
    }
  }, {
    "id": "2",
    "title": "The Parley Letter",
    "links": {
      "author": "9",
      "comments": [ "4", "5" ]
    }
  }, {
    "id": "3",
    "title": "Dependency Injection is Not a Virtue",
    "links": {
      "author": "9",
      "comments": [ "6" ]
    }
  }],
  "linked": {
    "people": [{
      "id": "9",
      "name": "@d2h"
    }],
    "comments": [{
      "id": "1",
      "body": "Mmmmmakase"
    }, {
      "id": "2",
      "body": "I prefer unagi"
    }, {
      "id": "3",
      "body": "What's Omakase?"
    }, {
      "id": "4",
      "body": "Parley is a discussion, especially one between enemies"
    }, {
      "id": "5",
      "body": "The parsley letter"
    }, {
      "id": "6",
      "body": "Dependency Injection is Not a Vice"
    }]
  }
}

After:

{
  "links": {
    "posts.author": {
      "href": "http://example.com/people/{posts.author}",
      "type": "people"
    },
    "posts.comments": {
      "href": "http://example.com/comments/{posts.comments}",
      "type": "comments"
    }
  },
  "posts": [{
    "id": "1",
    "title": "Rails is Omakase",
    "author": "9"
    "comments": [ "1", "2", "3" ]
  }, {
    "id": "2",
    "title": "The Parley Letter",
    "author": "9",
    "comments": [ "4", "5" ]
  }, {
    "id": "3",
    "title": "Dependency Injection is Not a Virtue",
    "author": "9",
    "comments": [ "6" ]
  }],
  "linked": {
    "people": [{
      "id": "9",
      "name": "@d2h"
    }],
    "comments": [{
      "id": "1",
      "body": "Mmmmmakase"
    }, {
      "id": "2",
      "body": "I prefer unagi"
    }, {
      "id": "3",
      "body": "What's Omakase?"
    }, {
      "id": "4",
      "body": "Parley is a discussion, especially one between enemies"
    }, {
      "id": "5",
      "body": "The parsley letter"
    }, {
      "id": "6",
      "body": "Dependency Injection is Not a Vice"
    }]
  }
}

Complex attributes

Before:

{
  "links": {
    "items.manufacturer": {
      "href": "http://example.com/manufacturers/{items.manufacturer}",
      "type": "manufacturers"
    },
    "items.cost.currency": {
      "href": "http://example.com/currencies/{items.cost.currency}",
      "type": "currencies"
    }
  },
  "items": {
    "id": "1",
    "description": "Portrait of Alexander Hamilton",
    "cost": {
      "amount": "10.00",
      "currency": "usd"
    },
    "links": {
      "manufacturer": "321"
    }
  }
}

After:

{
  "links": {
    "items.manufacturer": {
      "href": "http://example.com/manufacturers/{items.manufacturer}",
      "type": "manufacturers"
    },
    "items.cost.currency": {
      "href": "http://example.com/currencies/{items.cost.currency}",
      "type": "currencies"
    }
  },
  "items": {
    "id": "1",
    "description": "Portrait of Alexander Hamilton",
    "cost": {
      "amount": "10.00",
      "currency": "usd"
    },
    "manufacturer": "321"
  }
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions

      pFad - Phonifier reborn

      Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

      Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


      Alternative Proxies:

      Alternative Proxy

      pFad Proxy

      pFad v3 Proxy

      pFad v4 Proxy