Getting specific value from JSON data

Hi,

I have the following JSON data which lists services and some extras attached to each service:

"services":[
	{
		"id":101,
		"name":"Service 1 Name",
		"extras":[
			{
				"id":3,
				"name":"Extra Y",
			},
			{
				"id":8,
				"name":"Extra X",
			},
			{
				"id":12,
				"name":"Extra Z",
			},
		],
	},
]

From the above data I need to get the id value of Extra X within the extras. Namely, I need to get 8 from the above data. There may be a variable number of extras and their order may change, but the name Extra X stays the same. I know how to do it using a loop, but since there is a lot of data I need to go through, I am looking for a way to do it without a loop, hoping it will be faster / more efficient.

Thanks for any ideas.

you will always need some kind of loop, either explicitly with foreach() or implicitly with an array function (such as array_filter()).

Ok, with loops, I have the following which uses two foreach loops and two if statements, which is not what I would call as “ideal”:

foreach ($services->services as $service) {
	if ($service->name == 'Service 1 Name') {
		foreach ($service->extras as $extra) {
			if ($extra->name == 'Extra X') {
				$id = $extra->id
			}
		}
	}
}

Any suggestions how to use which array function(s) to get the data I need in a simpler way? Thanks.

what do you mean by “simpler”? jsonpath may be a solution, but comes with overhead, like anything else that is more abstract. but anywy you can just put your code in a function.

By simpler, I mean:

Get the id value of Extra X extra of Service 1 Name service from the given data, but do this without using multiple loops and IF statements.

For example, when getting a value from a multidimensional array you can do the following to get a specific value without needing a loop:

$users['id']['email']

EDIT: Thanks for the jsonpath suggestion but I am trying to keep things simpler, I wouldn’t want to complicate by adding further files or scripts.

But you do not know the indices for ‘Service 1 Name’ & ‘Extra X’ … therefore you can’t do it that way.

1 Like

Thanks, that’s what I was trying to figure out. You say it’s not possible. So, based on your personal view, if you needed to do what I need above, would you do it the way I did with the loops above? Or, is there a way to use some array functions to keep it 1 line or something? My purpose in coding is to do it more efficiently if possible, if not, to do it more elegantly.

jmespath.php was mentioned in the March post of Sourcehunt. It’s a nice implementation of the JMESPath query language for JSON. I’ve found it useful in the past for extracting information from JSON structures.

The example below may be of use. I’ve made some assumptions about your JSON structure though.

My purpose in coding is to do it more efficiently if possible, if not, to do it more elegantly.

I’m not sure how efficient jmespath.php is but the README goes into the different runtimes that it uses.

<?php

require __DIR__.'/vendor/autoload.php';

/**
 * Just for the example.
 * I assume your JSON string is obtained via other means.
 */
$jsonStr = <<< E_JSON
{
    "services": [{
        "id": 101,
        "name": "Service 1 Name",
        "extras": [{
            "id": 3,
            "name": "Extra Y"
        }, {
            "id": 8,
            "name": "Extra X"
        }, {
            "id": 18,
            "name": "Extra X"
        }, {
            "id": 28,
            "name": "Extra X"
        }, {
            "id": 12,
            "name": "Extra Z"
        }]
    }, {
        "id": 201,
        "name": "Service 2 Name",
        "extras": [{
            "id": 23,
            "name": "Extra Y"
        }, {
            "id": 28,
            "name": "Extra X"
        }, {
            "id": 212,
            "name": "Extra Z"
        }]
    }]
}
E_JSON;

$data = json_decode($jsonStr);

/**
 * See http://jmespath.org/tutorial.html for how expressions work.
 * The result is an array of the IDs for each extra that has the name "Extra X"
 */
$expression = "services[?name=='Service 1 Name'] | [].extras[?name=='Extra X'].id | []";
$result = JmesPath\search($expression, $data);
foreach ($result as $id) {
    printf("ID %s\n", $id);
}

/**
 * With a small adjustment to the expression we can get back each extras object.
 */ 
$expression = "services[?name=='Service 1 Name'] | [].extras[?name=='Extra X'] | []";
$result = JmesPath\search($expression, $data);
foreach ($result as $extra) {
    printf("Name %s ID %s\n", $extra->name, $extra->id);
}


/**
 * By removing the filter ?name=='Service 1 Name' from the expression
 * we can get all the IDs from any service.
 */
$expression = "services[] | [].extras[?name=='Extra X'].id | []";
$result = JmesPath\search($expression, $data);
foreach ($result as $id) {
    printf("ID %s\n", $id);
}

that’s two different tasks. with “efficiently” i think you mean better performance, like more data processed in the same time. but that does not strictly correlate with the amount of code, like your “1 liner”, which is about maintainability for the cost of abstraction, which you can achieve when using a custom function, or using a library.

Start by clarifying your requirements. In your opening post you said you just need the id of Extra X but the code you showed also filters by service name. Bit of a difference.

Secondly, will you be applying this filter more than once per request? In other words, getting the extra x id for multiple services? If so then it might be worth the overhead of transforming the json array into something that allows you to extract your info quickly.

And on a similar note, how often do you expect the json data itself to change? Is it possible to transform the data and then cache it for later use?

In my opening post I said “services”, which means my JSON data has multiple services, I didn’t list multiple services in my code sample for the sake of simplicity, perhaps I should have.

Yes, I need to get the “Extra X” id from multiple services in this JSON data.

“transforming the json array into something that allows you to extract your info quickly.” Other than the loops I used, I couldn’t think of a way to extract the info quickly, hence I asked it here.

The script will be used manually only after the JSON data is changed.

Hope these clarifies it.

@davidtsadler Thank you for the detailed sample. I really don’t want to use any extra script, library, file etc. The loop solution I have is my preferred option if I can’t find a simpler one.

This doesn’t use any loops. It does assume the data structure from my previous answer.

$data = json_decode($jsonStr, true);

$ids = array_reduce($data['services'], function ($carry, $service) {
    $extras = array_filter($service['extras'], function ($extra) { return $extra['name'] === 'Extra X'; });
    return array_reduce($extras, function ($carry, $extra) {
        $carry[] = $extra['id'];
        return $carry;
    }, $carry);
}, []);


print_r($ids);
/**
Array
(
    [0] => 8
    [1] => 18
    [2] => 28
    [3] => 28
)
*/

This topic was automatically closed 91 days after the last reply. New replies are no longer allowed.