I'm in the process of building a REST API and all data is being returned as JSON. Each request is funneled through a single function that does the job of setting HTTP status codes, returning messages or data, setting headers, etc. I also allow users to add a ?fields= parameter where they can specify what fields they want returned (e.g ?fields=id,hostnames,ip_addresses), if the parameter is not present they of course get all the data returned. The function that does this is also part of the function mentioned earler that sets the headers/data/messages, etc. What I want to be able to do is allow the user to specify field names using dot notation so they can specify fields from something other than a top-level field. So for example, I have a structure like this:
{
"id": "8a2b449111b449409c465c66254c6fcc",
"hostnames": [
"webapp1-sfo",
"webapp1-sfo.example.com"
],
"ip_addresses": [
"12.26.16.10",
"ee80::ae56:2dff:fd89:7868"
],
"environment": "Production",
"data_center": "sfo",
"business_unit": "Operations",
"hardware_type": "Server",
"currently_in_maintenance": false,
"history": [
{
"id": 58,
"time_start_utc": "2013-01-27 00:40:00",
"time_end_utc": "2013-01-27 01:45:00",
"ticket_number": "CHG123456",
"reason": "January production maintenance",
"links": [
{
"rel": "self",
"link": "https://localhost/api/v1/maintenances/58"
}
]
},
{
"id": 104,
"time_start_utc": "2013-02-25 14:36:00",
"time_end_utc": "2013-02-25 18:36:00",
"ticket_number": "CHG456789",
"reason": "February production maintenance",
"links": [
{
"rel": "self",
"link": "https://localhost/api/v1/maintenances/104"
}
]
},
{
"id": 143,
"time_start_utc": "2013-03-17 00:30:00",
"time_end_utc": "2013-03-17 01:55:00",
"ticket_number": "CHG789123",
"reason": "March production maintenance",
"links": [
{
"rel": "self",
"link": "https://localhost/api/v1/maintenances/143"
}
]
}
]
}
Using this function, I can pull out top level fields (where $mData is the data structure above, and $sParams is the string of fields requested by the user):
private function removeFields($mData, $sParams){
$clone = $mData; // Clone the original data
$fields = explode(',', $sParams);
// Remove fields not requested by the user
foreach($mData as $key => $value){
if(!in_array((string)$key, $fields)){
unset($mData[$key]);
}
}
// If no fields remain, restore the original data
// Chances are the user made a typo in the fields list
if(count($mData) == 0){
$mData = $clone;
}
return $mData;
}
Note: $sParams comes in as a string and is what is provided by the user (comma separated list of fields they want to see).
So ?fields=hostnames,history would return:
{
"hostnames": [
"webapp1-sfo",
"webapp1-sfo.example.com",
],
"history": [
{
"id": 58,
"time_start_utc": "2013-01-27 00:40:00",
"time_end_utc": "2013-01-27 01:45:00",
"ticket_number": "CHG123456",
"reason": "January production maintenance",
"links": [
{
"rel": "self",
"link": "https://localhost/api/v1/maintenances/58"
}
]
},
{
"id": 104,
"time_start_utc": "2013-02-25 14:36:00",
"time_end_utc": "2013-02-25 18:36:00",
"ticket_number": "CHG456789",
"reason": "February production maintenance",
"links": [
{
"rel": "self",
"link": "https://localhost/api/v1/maintenances/104"
}
]
},
{
"id": 143,
"time_start_utc": "2013-03-17 00:30:00",
"time_end_utc": "2013-03-17 01:55:00",
"ticket_number": "CHG789123",
"reason": "March production maintenance",
"links": [
{
"rel": "self",
"link": "https://localhost/api/v1/maintenances/143"
}
]
}
]
}
But if I want to return maybe just the ticket_number field from history I want the user to be able to do ?fields=history.ticket_number or if they want the ticket number and link they could do this: ?fields=history.ticket_number,history.links.link...which would return:
{
"history": [
{
"ticket_number": "CHG123456",
"links": [
{
"link": "https://localhost/api/v1/maintenances/58"
}
]
},
{
"ticket_number": "CHG456789",
"links": [
{
"link": "https://localhost/api/v1/maintenances/104"
}
]
},
{
"ticket_number": "CHG789123",
"links": [
{
"link": "https://localhost/api/v1/maintenances/143"
}
]
}
]
}
I've tried many different array access methods for dot notation from stack overflow but they all break when the value of history is a numeric array...so for instance, using the methods I've found online so far I would need to do something like this to achieve the same output above (which obviously is not good...especially when you have hundreds of records).
?fields=history.0.ticket_number,history.0.links.0.link,history.1.ticket_number,history.1.links.0.link,history.2.ticket_number,history.2.links.0.link,
I was also looking for something that was dynamic and recursive as each API endpoint returns a different data structure (for instance, when a collection is requested it returns a numeric array filled with associative arrays..or in json speak, an array of objects...and some of those objects may have arrays (numeric or associative)).
Thanks in advance
P.S. - I don't really care if the code creates a new data array containing the requested data or directly manipulates the original data (as it does in my removeFields() function).
UPDATE: I've created a PHPFiddle that should hopefully show the issue I've been running into. http://phpfiddle.org/main/code/tw1i-qu7s
fieldsparameter via GET that will be processed on the server side with PHP. If it is provided it may contain a comma separated list of top-level fields (?fields=hostnames,data_center,history) or a dot-delineated list of values to extract data at a lower level (?fields=history.id,history.ticket_number) or both (?fields=hostnames,data_centers,history.ticket_number). Basically, the user could provided any combination of available array keys, each being separated by a comma....which I then explode into an array and process.