JSON Hypermedia Api with forms and links

2020-02-19 05:36发布

I am in the early stages of planning a REST api, and I would like for it to adhere to the HATEOAS constraint of REST. But I would also like to provide a JSON format. So my question is if there are conventions out there to represent links and forms in JSON.

I have found examples of links, and it looks like this is a pretty common way of representing links:

"links": [ 
{"rel": "self", "href":"http://example.org/entity/1"},
{"rel": "friends", "href":"http://example.org/entity/1/friends"}] 

Representing forms on the other hand, is not something that I have seen much of. I was thinking that perhaps somebody had sat down and thought up something along these lines, but considered all the caveats:

"forms" : [
{"rel" : "new client", "action" : "/clients", "method": "post", 
"fields" : ["name":"string", "zipcode":"int", "signedup":"date", "state": ["Alabama",...]...]}]

The inspiration for this comes from looking at this video, where Jon Moore suggests that JSON is not a good format for a hypermedia api:

http://oredev.org/2010/sessions/hypermedia-apis

A really good talk by the way!

All input is appreciated!

5条回答
Root(大扎)
2楼-- · 2020-02-19 06:23

Check out Collection+JSON, HAL, and/or Siren.

查看更多
走好不送
3楼-- · 2020-02-19 06:24

There currently isn't a publicly specced, general purpose JSON format with forms, as far as I am aware. You're free to define one if you need it and publish the specifcations. As a personal preference, I recommend basing it upon HAL.

If you do decide to write one of your own, please create a mailing list and invite others to participate. If you don't, you'd be in danger of tailoring it too closely to meet just your own needs and accidentally overlooking some requirement that prevents it from being widely applicable.

查看更多
Luminary・发光体
4楼-- · 2020-02-19 06:26

I have investigated this topic for a while, but I am not certain about which possible solutions ppl use and which not. There are only a few examples available... So I'll need some review from experts... (My examples will be mostly in HAL+JSON.)

1.)

I have a feeling that link relations should be GET only, because in HTML they are for including things like stylesheets. I guess other ppl had the same feeling, because there is an edit-form and a create-form by IANA link relations.

  • So the first possible solution to dereference links with form relations and so download the form descriptions for the write operations. These form descriptions can contains HTML fragments or a schema which we can use to generate the forms. For example

    Just to mention you can use the same approach by sending the same links in the header and send raw JSON as body.

    {
        "_links": {
            "edit-form": {
                "href": "http://example.com/users/1?form=edit",
                "type": "text/html",
                "title": "Edit user"
            }
        }
    }
    

So what if link relations are not just for read purposes?

2.)

Then we can use the built-in features of HAL:

  • If we send data, then we can use the type to describe the request body instead of the response body. Ofc. in this case there should not be a response body, or this solution will be confusing.

        {
            "_links": {
                "curies": [
                    {
                        "name": "my",
                        "href": "http://example.com/rels/{rel}",
                        "templated": true
                    }
                ],
                "my:edit": {
                    "href": "http://example.com/users/1",
                    "type": "application/vnd.example.user+json",
                    "title": "Edit user"
                }
            }
        }
    

    So in this case the client will know that my:edit means that this is an edit form, and by checking the MIME type it will know what type of form to display.

  • An alternative solution to use the custom link relation for the same purpose:

        {
            "_links": {
                "curies": [
                    {
                        "name": "my",
                        "href": "http://example.com/rels/{rel}",
                        "templated": true
                    }
                ],
                "my:edit-user": {
                    "href": "http://example.com/users/1",
                    "type": "application/json",
                    "title": "Edit user"
                }
            }
        }
    

    So by fetching the docs http://example.com/rels/edit-user we can find a description about how to build a form for editing users and so we can support the my:edit-user link relation in our client. The docs can contain optionally a HTML form or some schema, or an RDF document using a form description vocab, etc...

  • We can follow the same approach by the profile property of the links. For example:

        {
            "_links": {
                "curies": [
                    {
                        "name": "my",
                        "href": "http://example.com/rels/{rel}",
                        "templated": true
                    }
                ],
                "my:edit": {
                    "href": "http://example.com/users/1",
                    "type": "application/json",
                    "title": "Edit user",
                    "profile": "http://example.com/profiles/user"
                }
            }
        }
    

    So in here the link relation means that this is an edit form and the profile describes how to generate the form under the http://example.com/profiles/user URL.

3.)

Or we can extend HAL using custom properties.

  • For example dougrain-forms does this:

        {
            "_forms": {
                "edit": {
                    "href": "http://example.com/users/1",
                    "headers": {
                        "content-type": "application/json"
                    },
                    "title": "Edit user",
                    "method": "PUT",
                    "schema": {
                        "required": [
                            "name"
                        ],
                        "type": "object",
                        "properties": {
                            "name": {
                                "type": "string"
                            }
                        },
                        "title": "user properties"
                    }
                }
            }
        }
    
  • But you can use any alternative approach as long as we don't have a standard about HAL and about HAL forms, for example I would rather use a mongoose schema like solution:

        {
            "name": "John",
            "_links": {
                "curies": [
                    {
                        "name": "my",
                        "href": "http://example.com/rels/{rel}",
                        "templated": true
                    }
                ],
                "my:edit": {
                    "href": "http://example.com/users/1",
                    "type": "application/json",
                    "title": "Edit user",
                    "method": "PUT",
                    "_embedded": {
                        "schema": {
                            "name": "String"
                        }
                    }
                }
            }
        }
    

4.)

Don't use link relations and simple JSON formats like HAL, use RDF with one or more vocabulary instead. It is harder to use RDF, but it is a fine grained solution for decoupling clients from REST services, while HAL is just a coarse grained solution...

  • For example JSON-LD with Hydra and a custom vocab:

    {
        "@context": [
            "http://www.w3.org/ns/hydra/core",
            "https://example.com/docs#"
        ],
        "@id": "https://example.com/users/1",
        "name": "John",
        "operation": {
            "@type": "ReplaceResourceOperation",
            "title": "Edit user",
            "method": "PUT",
            "expects": {
                "@id": "https://example.com/docs#User",
                "supportedProperty": {
                    "@type": "SupportedProperty",
                    "title": "name",
                    "property": "https://example.com/docs#User.name",
                    "range": "http://www.w3.org/2001/XMLSchema#string",
                    "required": true
                }
            }
        }
    }
    
查看更多
Viruses.
5楼-- · 2020-02-19 06:31

The JSON Schema standard (particularly "hyper-schemas") definitely allows this. You reference a JSON (Hyper-)Schema (using HTTP headers) and the schema defines rules on how to interpret your data as hyper-text.

The information for constructing your links can be anywhere. The hyper-schema documents how to assemble link URIs from the data (it can be a template), and they also specify HTTP method, encoding type, and so on.

To get form functionality: you can specify a full schema for the data to be submitted along with the request. Required/optional properties, array length constraints, whatever.

As a demo, here's part of a walkthrough for a JavaScript library that understands hyper-schemas and can present an appropriate form for links: jsonary.com.

查看更多
Rolldiameter
6楼-- · 2020-02-19 06:35

I have been working on an API, using JSON Hyper Schema. You can browse aroun, and even register, login and do some actions. Check it out, here: http://api.psprt.com

[EDIT] See my latest stuff here: www.passportedu.com https://github.com/bpanahij/HypermediaServer https://github.com/bpanahij/client-schema.json

I also open sourced the API code: https://github.com/bpanahij/passportedu_schema

Feel free to take a look, borrow, and comment.

JSON Hyper Schema (See Also JSON-Schema) has a way to specify forms, through the properties member:

{
"id": "/api/v1",
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "PassportEDU API",
"name": "PassportEDU API",
"type": "object",
"description": "Bringing global students together with global schools.",
"links": [
   {
      "title": "Log In",
      "rel": "authenticate",
      "href": "/api/v1/authenticate",
      "method": "POST",
      "properties": {
        "username": {
          "title": "Your username",
          "description": "Your email address or username",
          "type": "string"
        },
        "password": {
          "title": "Your password",
          "description": "Your password",
          "type": "password"
        }
      },
      "required": ["username", "password"]
   }
   ]
}
查看更多
登录 后发表回答