Jump to content
NotionCommotion

Name/value pairs or key/value properties for REST API update

Recommended Posts

I implemented an endpoint which receives name/value pairs:

PUT someresource/123 {"name": "description", "value": "Some new description"}

I think I made a mistake, and should have implemented it as:

PUT someresource/123 {"description": "Some new description"}

The reason I think so is updating multiple properties is only (easily) possible using the later:

PUT someresource/123 {"description": "Some new description", "otherProperty": "bla bla bla"}

Assuming I am not using some 3rd party client library which only works with name/value pairs, any compelling reason why one shouldn't default to the later approach with key/values?

Share this post


Link to post
Share on other sites

It should be either

[{"name": "description", "value": "Some new description"}, ...]

or

{"description": "Some new description", ...}

The second one tends to be much nicer to work with.

Share this post


Link to post
Share on other sites

Before moving on...

The following seems clean enough:

//PUT /some/resource/123   body: {"description": "Some new description", "name": "new name" ...}
$app->get('/some/resource/{id:[0-9]+}', function (Request $request, Response $response, $args) {
    $resource=$this->getResource($args['id']);
    $params=$request->getParsedBody();
    foreach($params as $name=>$value) {
        $resource->$name=$value; //but implement more securely
    }
});

If I only wanted to update a single property (i.e. {"description": "Some new description"} ), the same server code works.

But I can also change the client request and do something like the following.

//PUT /some/resource/123/description   body: Some new description
$app->get('/some/resource/{id:[0-9]+}/{property}', function (Request $request, Response $response, $args) {
    $resource=$this->getResource($args['id']);
    $value=$request->getBody();
    $resource->$args['property']=$value;  //but implement more securely
});

Under normal circumstances, probably doesn't make sense.  Agree?

What if I wasn't updating some value stored in the DB but some "option" object which is stored as MySQL type JSON and wanted to keep it more flexible?

{
	"title": {
		"text": "Activity",
		"style": {
			"fontSize": "24px"
		}
	},
	"tooltip": {
		"borderWidth": 0,
		"backgroundColor": "none",
		"shadow": false,
		"style": {
			"fontSize": "16px"
		},
		"pointFormat": "{series.name}<br><span style=\"font-size:2em; color: {point.color}; font-weight: bold\">{point.y}%</span>"
	},
	"pane": {
		"startAngle": 0,
		"endAngle": 360,
		"background": [{
				"outerRadius": "112%",
				"innerRadius": "88%",
				"backgroundColor": "rgba(124,181,236,0.3)",
				"borderWidth": 0
			}, {
				"outerRadius": "87%",
				"innerRadius": "63%",
				"backgroundColor": "rgba(67,67,72,0.3)",
				"borderWidth": 0
			}, {
				"outerRadius": "62%",
				"innerRadius": "38%",
				"backgroundColor": "rgba(144,237,125,0.3)",
				"borderWidth": 0
			}
		]
	},
	"yAxis": {
		"min": 0,
		"max": 100,
		"lineWidth": 0,
		"tickPositions": []
	},
	"plotOptions": {
		"solidgauge": {
			"dataLabels": {
				"enabled": false
			},
			"linecap": "round",
			"stickyTracking": false,
			"rounded": true
		}
	}
}

I was thinking of something like the following to implement:

//PUT /some/resource/123/option/foo/bar/someproperty   body: TBD
$app->get('/some/resource/{id:[0-9]+}/option/{subproperties:.*}', function (Request $request, Response $response, $args) {
    $resource=$this->getResource($args['id']);
    $properties=explode('/', $args['subproperties']);
    //Update specific property in $resource->option using $properties
});

For this it kind of makes sense to target the specific property, but this is contrary to the earlier approach.  Or maybe I should keep with the {name: value} approach?

Share this post


Link to post
Share on other sites

Can't say I agree with that approach. It's too weird. Stick with the standard: the data in the request body.

If, however, modifying one or three specific properties is a common use case then that suggests your API should support it with a specific operation.

Share this post


Link to post
Share on other sites
Posted (edited)
12 hours ago, requinix said:

Can't say I agree with that approach. It's too weird. Stick with the standard: the data in the request body.

Just to make sure I was correctly communicating on my previous post, to change option.plotOptions.solidgauge.dataLabels.enabled from false to true, I would do one of the following requests:

PUT /some/resource/123/option/plotOptions/solidgauge/dataLabels   body: {enabled: true}
//or
PUT /some/resource/123/option/plotOptions/solidgauge/dataLabels/enabled   body: true

//If this is an acceptable approach, which request should be used?

and then get resource 123, get the option property, and finally use [plotOptions, solidgauge, dataLabels, enabled] to target the specific property.  Still too weird?

And to make sure I understand you, stick with the standard where the entire option JSON string is in the body, right?

Edited by NotionCommotion

Share this post


Link to post
Share on other sites

Yes. PUT should be the entire body, PATCH should be whatever the changes are, and POST can sorta be either.

So if you want to support a generic API for updating specific properties, use PATCH.

PATCH /some/resource/123 HTTP/1.1
Content-Type: application/json

{"option":{"plotOptions":{"solidgauge":{"dataLabels":{"enabled":true}}}}}

Unless you think a sort of "enable/disable chart label" API would make more sense, along the lines of /toggle/label/name/state:

POST /some/resource/123/toggle/label/solidgauge/enabled HTTP/1.1

 

Share this post


Link to post
Share on other sites

Ah, that is when I use PATCH!  I've never used the method, but was actually planning on using this approach but as PUT.  I even came up with the following to use in concert with xeditable.

function conv2obj(keys, value, object) {
    keys=path.split('.');
    object=object?object:{};
    var last = keys.pop();
    keys.reduce((o, k) => o[k] = o[k] || {}, object)[last] = value;
    return object;
}

var o=convert('option.plotOptions.solidgauge.dataLabels.enable', true);
//{"option":{"plotOptions":{"solidgauge":{"dataLabels":{"enable":true}}}}}

Shouldn't your second example us PUT since it is idempotent?  It doesn't replace the entire /some/resource/123 resource, but it does replace the entire /some/resource/123/toggle/label/solidgauge/enabled resource.

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×

Important Information

We have placed cookies on your device to help make this website better. You can adjust your cookie settings, otherwise we'll assume you're okay to continue.