Create JSON using PHP with data which contains JSON - php

How can I create JSON using PHP with data which contains JSON without including a bunch of escape characters in the JSON, and without converting JSON first to an array or object, and then back to JSON?
<?php
/*
GIVEN: Data from DB contained in array $a.
I know that sometimes JSON shouldn't be stored in a DB, but please assume this is a good case for doing so.
*/
$a[0]=json_encode(['a'=>5,'b'=>'hello']);
$a[1]=json_encode(['a'=>2,'b'=>'how are you']);
$a[2]=json_encode(['a'=>7,'b'=>'goodby']);
$o=[
['x'=>321,'y'=>$a[0]],
['x'=>123,'y'=>$a[1]],
['x'=>111,'y'=>$a[2]],
];
echo('<pre>'.print_r($o,1).'</pre>');
echo(json_encode($o));
/*
Undesired result containing a bunch of escape characters. Granted, they are benign, however, will increase network trafic.
[{"x":321,"y":"{\"a\":5,\"b\":\"hello\"}"},{"x":123,"y":"{\"a\":2,\"b\":\"how are you\"}"},{"x":111,"y":"{\"a\":7,\"b\":\"goodby\"}"}]
*/
$o=[
['x'=>321,'y'=>json_decode($a[0])],
['x'=>123,'y'=>json_decode($a[1])],
['x'=>111,'y'=>json_decode($a[2])],
];
echo('<pre>'.print_r($o,1).'</pre>');
echo(json_encode($o));
/*
Desired result, however, is there a more efficient way to do this?
[{"x":321,"y":{"a":5,"b":"hello"}},{"x":123,"y":{"a":2,"b":"how are you"}},{"x":111,"y":{"a":7,"b":"goodby"}}]
*/

No, there is no faster way then to decode, mutate and encode again.
You could however combine the decoding code closely with your database queries. If you have a data model class, you would do the decoding there, so that the code that calls this service will never see the JSON, but always the decoded data.
Then, again, in the data model, where you write to the database, you would perform the JSON encoding at the last moment.
This way you hide the JSON factor behind the walls of the data layer, and the rest of the application will not have to be aware of that.
Manipulating JSON directly
Another solution consists of writing a library that can juggle with JSON, giving possibilities to set values inside JSON without the caller having to decode/encode. This option requires much more code (or you could find an existing library), and so it is not really my first recommendation. But here is a naive example of a function that could exist in that library:
function json_set($json, $path, $value) {
$arr = json_decode($json, true);
$ref =& $arr;
$props = explode("/", $path);
$finalProp = array_pop($props);
foreach ($props as $key) {
if (!isset($ref[$key])) $ref[$key] = [];
$ref =& $ref[$key];
}
$obj = json_decode($value);
$ref[$finalProp] = $obj ? $obj : $value;
return json_encode($arr);
}
This allows you to provide an existing JSON string, a path pointing to a certain spot inside that JSON, and a value that should be put there. That value could itself also be JSON.
Here is how you would use it in your case, given the JSON values in $a which you got from the database:
$o=json_encode([
['x'=>321],
['x'=>123],
['x'=>111],
]);
$o = json_set($o, '0/y', $a[0]);
$o = json_set($o, '1/y', $a[1]);
$o = json_set($o, '2/y', $a[2]);
echo $o;
Output:
[{"x":321,"y":{"a":5,"b":"hello"}},
{"x":123,"y":{"a":2,"b":"how are you"}},
{"x":111,"y":{"a":7,"b":"goodby"}}]

Related

Filter API response with PHP

Not sure what issue was with first pastebin code, here is another attempt.
I am connecting to Vimeo Live API, in doing so the response is huge > 500kb in total - I have an example with only one object here -> there are over 20 it returns. I have a better idea of what Im doing in JS than PHP, but returning the huge array or json to the browser doesn't seem like a good idea and its use of repeated ajax calls isn't good either. So this question is two fold, both in theory and practice. Is this a good design and how do I filter the result in PHP and only send what I need back to browser.
Here is the design, or at least what I think is best:
page loads, sends ajax request to PHP script
PHP script connects to API and gets response in an array (example of one object)
Search through the array for 'metadata->connections->live_video' for one that has an associated array containing [status] => streaming'
If one (there will only be one at a time) is found, return that whole object and that object only, not the entire array.
At this time I do not have a complete understanding of how this data should be returned or formatted for ease of sifting through. Ive tried using json_encode on the array, which gets nicely formatted JSON but I can't iterate through it and can only get single objects like data[0]->metadata->connections->live_video. Ive tried json_encode, then json_decode and Im back to a similar array structure of what is originally sent.
However, in the browser I am able to return the whole array and in the success function of the ajax call sift through it via JS like so:
let live_stream = json.data.filter(function(value, key) {
let connection = value.metadata.connections['live_video'];
return connection && connection.status === 'streaming';
});
I know this isn't the right way, I know I need to sift through the array, find the object / key Im looking for and only return that. Any advice is appreciated, once I get this figured out, I can apply it in a range of ways for this project.
The closest I can get in PHP is:
function live_event() {
global xxx;
$lib = xxx;
$response = xxx;
$body = $response['body'];
header('Content-Type: application/json');
$jsonstr = json_encode($body);
$json = json_decode($jsonstr);
foreach ($json->data as $item) {
if ($item->uri == "/live_events/2354796") {
echo"one";
}
}
}
and this:
function live_event() {
$global xxx;
$lib = xxx;
$response = xxx;
$body = $response['body'];
header('Content-Type: application/json');
$jsonstr = json_encode($body);
$json = json_decode($jsonstr,true);
$results = array_filter($json['data'], function($item) {
return $item['metadata']['connections']['live_video']['status'] == "streaming";
});
var_dump($results);
}
last one gets me this error "Warning: Trying to access array offset on value of type null in /var/www/vhosts/mysite.com/httpdocs/SSI/Vimeo.php on line 31" is there something similar to optional chaining in PHP? if its null I don't want it to log an error.
This at least output "one" as there is only one object with [uri]=>"/live_events/2354796". I can't get it to return that entire object or search one more nested array deeper.
There are a few things that come to mind.
OBJECTS:
If you're using PHP 8 you could use the safe access operator
function live_event() {
//...
$results = []
foreach ($json->data as $item) {
if($item?->metadata?->connections?->live_video?->status == 'streaming'){
$results[] = $item;
}
}
return $results;
}
ARRAYS:
For lower versions of PHP it's better to work with arrays when dealing with keys that may or may not exists.
Generally speaking whatever fetch library you're working with will allow you set the output to either array or object. If Object is your only option then yes doing the old json_decode(json_encode($data), true) is the way to go.
checking first isset(). Assuming that's it's desired to filter out results without that key.
function live_event() {
//...
$results = array_filter($json['data'], function($item) {
if(!isset($item['metadata']['connections']['live_video']['status']))
return false;
return $item['metadata']['connections']['live_video']['status'] == "streaming";
});
header('Content-Type: application/json');
echo json_encode([
"status" => true,
"data" => $results
]);
}
or you can always just use the age old trick of suppressing the error with the # symbol. TBH i'm not sure where the # symbol would go to suppress the error, perhaps in front of the filter function definition.
function live_event() {
//...
$results = array_filter($json['data'], function($item) {
return #$item['metadata']['connections']['live_video']['status'] == "streaming";
});
return $results;
}

Finding titles in JSON

I currently have this large JSON file: hastebin
But just want the titles of the posts.
I've tried this...
$json = $page;
$o = json_decode($json, true);
echo($json);
$titles = $o["*"]["*"]["*"]["*"]["title"];
var_dump($titles);
But it isn't working - it's returning NULL! Sometimes it just doesn't return anything.
If anyone is wondering, yes this is from Reddit.
This should do it:
$titles = array_map(function($post) {
return $post['data']['title'];
}, $o['data']['children']);
I'm not sure what you expected using "x" indices, but you should probably read about arrays.
PHP can't use wildcards like * in array keys. Whatever string you use to reference the key, it's going to try to find a key with that exact string. So what you tried can't work because there aren't any * keys.
You can get it by iterating all the levels, or iterating the outer level and referring to the proper nested key. But if you're just looking for all instances of 'title' a recursive method may be an easier way to get them.
array_walk_recursive($o, function($value, $key) use (&$titles) {
if ($key == 'title') $result[] = $value;
});
var_dump($titles);
This will get any value of 'title' regardless of its depth in the array, so if that's not what you want, then you'll need to iterate it and specifically reference the proper ones.
It's very hard to deal directly with such a long JSON document. The returned result from the page is not a valid JSON. It contains some HTML tags, but if you take the posts data and insert it in a file you can do the following according to the structure of your JSON (You can find your JSON in an external link here):
<?php
header("Content-Type:application/json");
$posts=file_get_contents('json.php');
//decode your JSON STRING
$posts=json_decode($posts,true);
//create a title variable to store your titles
$titles=array();
foreach($posts['data']['children'] as $child)
{
array_push($titles,$child['data']['title']);
}
echo json_encode($titles);
?>
You can even use this approach using a URL but ensure that it will return a valid JSON with no html

json_encode changing array with one value to object

I"m creating a PHP script that handles JSON input (via a $_POST variable). It"s extracts data from the JSON and uploads it to an SQL database. I want the JSON in a particular format:
$object = json_decode('{
"key_a":[{"value_a":10,"value_b":7},{"value_a":10,"value_b":7},{"value_a":10,"value_b":7}],
"key_b":[{"value_a":10,"value_b":7}],
"key_c":[{"value_a":10,"value_b":7},{"value_a":10,"value_b":7}]
}',true);
Basically, an object with keys in, each of which should hold an array (no matter what size it is). I use json_decode(json,true) to convert it to an associative array (as opposed to object). I"ve had to add lots of checks in for each of the keys, checking if they"re objects or arrays (as the ASP.net page that the extract comes from converts arrays with single objects in, to objects - removing the array that holds them). The checks then convert them back to arrays, if there"s an object where I"d like an array holding an object:
if(is_object($object["key_b"]))
{
$a = array();
$a[] = $object["key"];
$object["key"] = $a;
}
I then iterate through the array, adding the values to rows in an SQL database. This all works fine, but when converting back to JSON with json_encode, any keys that hold arrays with only one object in, remove the array, and leave just the object under that key:
echo(json_encode($object));
// RETURNED JSON
'{
"key_a":[{"value_a":10,"value_b":7},{"value_a":10,"value_b":7},{"value_a":10,"value_b":7}],
"key_b":{"value_a":10,"value_b":7},
"key_c":[{"value_a":10,"value_b":7},{"value_a":10,"value_b":7}]
}'
You see, key_b no longer holds an array, but an object! This is really annoying, as I plan to create a JavaScript script that iterates through the arrays, adding one DOM element (div) for each of the objects.
Why does this happen? Is there any way to keep them as arrays, even if there"s only one object in the array?
I"ve tried:
if(is_object($object["key_b"]))
{
$a = array();
$a[] = array_values($object["key"]);
$object["key"] = $a;
}
and
if(is_object($object["key_b"]))
{
$a = array();
$a[0] = array_values($object["key"]);
$object["key"] = $a;
}
But it seems like nothing prevents json_encode from affecting the JSON in this way.
It"s not hard to get around this - but it means adding one check per key (checking whether it"s an array or value), which is particularly time consuming as the data extract that comes through is really big.
Any advice would be greatly appreciated.
EDIT: changed ' to " in JSON - though, this is only an example I just wrote to show the structure.
EDIT: I'm using references to cut my coding time down, if this changes anything?:
$t =& $object["key_b"];
if(is_object($t))
{
$a = array();
$a[] = $t;
$t = $a;
}
It appears using is_object() on a key of an associative array will not return true. I just knocked up this example, to prove this:
$json = json_decode('{"job_details":{"a":[{"x":5},{"y":23},{"z":18}],"b":{"x":19},"c":[{"x":64},{"y":132}]}}',true);
echo(json_encode($json)."<br><br>");
$t =& $json["job_details"]["b"];
if(is_object($t))
{
$a = array();
$a[] = $t;
$t = $a;
echo("IS OBJECT<br><br>");
}
echo(json_encode($json));
I will find another means of checking what value is held within an associative arrays key.
I was actually trying to find whether the value in the key is an associative array or not (not an object) - I just didn't realise they were different in PHP.
I must just use this custom function:
function is_assoc($array)
{
return (bool)count(array_filter(array_keys($array), 'is_string'));
}
From: How to check if PHP array is associative or sequential?
Which returns true if the value is an associative array.

Access JSON values in PHP

{"coord":{"lon":73.69,"lat":17.8},"sys":{"message":0.109,"country":"IN","sunrise":1393032482,"sunset":1393074559},"weather":[{"id":800,"main":"Clear","description":"Sky is Clear","icon":"01n"}],"base":"cmc stations","main":{"temp":293.999,"temp_min":293.999,"temp_max":293.999,"pressure":962.38,"sea_level":1025.86,"grnd_level":962.38,"humidity":78},"wind":{"speed":1.15,"deg":275.503},"clouds":{"all":0},"dt":1393077388,"id":1264491,"name":"Mahabaleshwar","cod":200}
I am trying to fetch description from the weather from the json above but getting errors in php. I have tried the below php code:
$jsonDecode = json_decode($contents, true);
$result=array();
foreach($jsonDecode as $data)
{
foreach($data{'weather'} as $data2)
{
echo $data2{'description'};
}
}
Any help is appreciated. I am new in using json.
You have to use square brackets ([]) for accessing array elements, not curly ones ({}).
Thus, your code should be changed to reflect these changes:
foreach($data['weather'] as $data2)
{
echo $data2['description'];
}
Also, your outer foreach loop will cause your code to do something completely different than you intend, you should just do this:
foreach($jsonDecode['weather'] as $data2)
{
echo $data2['description'];
}
Your $jsonDecode seems to be an array, so this should work-
foreach($jsonDecode['weather'] as $data)
{
echo $data['description'];
}
You can access data directly with scopes
$json = '{"coord":{"lon":73.69,"lat":17.8},"sys":{"message":0.109,"country":"IN","sunrise":1393032482,"sunset":1393074559},"weather":[{"id":800,"main":"Clear","description":"Sky is Clear","icon":"01n"}],"base":"cmc stations","main":{"temp":293.999,"temp_min":293.999,"temp_max":293.999,"pressure":962.38,"sea_level":1025.86,"grnd_level":962.38,"humidity":78},"wind":{"speed":1.15,"deg":275.503},"clouds":{"all":0},"dt":1393077388,"id":1264491,"name":"Mahabaleshwar","cod":200}';
$jsonDecode = json_decode($json, true);
echo $jsonDecode['weather'][0]['description'];
//output Sky is Clear
As you can see wheater` is surrounded with scopes so that means it is another array. You can loop throw that array if you have more than one result
foreach($jsonDecode['weather'] as $weather)
{
echo $weather['description'];
}
Live demo
If the result of decode is an array, use:
$data['weather']
If the result is an object, use:
$data->weather
you have to access "weather" with "[]" operator
like this,
$data["weather"]
There is several things worth answering in your question:
Q: What's the difference between json_decode($data) and json_decode($data, true)?
A: The former converts JSON object to a PHP object, the latter creates an associative array: http://uk1.php.net/json_decode
In either case, there is no point on iterating over the result. You probably want to access just the 'weather' field:
$o = json_decode($data) => use $weather = $o->weather
$a = json_decode($data, true) => use $weather = $a['weather']
Once you have the 'weather' field, look carefully what it is:
"weather":[{"id":800,"main":"Clear","description":"Sky is Clear","icon":"01n"}]
It's an array, containing a single object. That means you will either need to iterate over it, or use $clearSky = $weather[0]. In this case, it does not matter which approach of json_decode did you choose => JSON array is always decoded to a PHP (numeric indexed) array.
But, once you get $clearSky, you are accessing the object and it again matters, which approach you chose - use arrow or brackets, similarly to the first step.
So, the correct way to get for exaple the weather description would be either of these:
json_decode($data)->weather[0]->description
json_decode($data, true)['weather'][0]['description']
Note: In the latter case, dereferencing result of the function call is supported only in PHP 5.4 or newer. In PHP 5.3 or older, you have to create a variable.
Note: I also encourage you to always check if the expected fields are actually set in the result, using isset. Otherwise you will try to access undefined field, which raises an error.

parse GeoJson String in PHP

I want to get polygon coordinates from below String.
{"polygon":{"type":"Feature","properties":[],"geometry":{"type":"Polygon","coordinates":[[[-7302732.4720101,6527844.6333235],[-3193477.8319711,6606116.1502766],[-5111129.9973226,5001550.0527375],[-6637424.5779086,4884142.7773079],[-7772361.5737289,5158093.0866438],[-7302732.4720101,6527844.6333235]]]},"crs":{"type":"name","properties":{"name":"urn:ogc:def:crs:OGC:1.3:CRS84"}}}}
This is GeoJson string that i decode to array with below code:
$polygon = CJSON::decode($str);
when i want to get polygon i get error!
$var= $polygon->polygon;
or with below code:
$polygon = CJSON::decode($str,true);
$var = $polygon['polygon'];
although for getting coordinates:
foreach($polygon as $key=>$value)
$coordinates = $value['coordinates'];
or
$coordinates = $value[coordinates];
how can i get coordinates from geojson that i send from javascript to php for saving on postgresql with postgis?
$polygon->polygon->geometry->coordinates[0]
or
$polygon['polygon']['geometry']['coordinates'][0]
what you have is a multidimensional array/object not sure which its being output to when decoded in your case as it appears you have a class doing it I would have just used json_decode, but anyway. Yea from the looks of it, polygon is the main object, then in it is geometry which is an object that has type and coordinates, and then coordinates has multiple objects/arrays in it.
the above samples if I typed them right will show the first set of coordinates in that object. Of course you could run it through a loop ie:
In the case that it is an object assuming your Class decodes as an object and not an array. Not exactly sure what $polygon = CJSON::decode($str,true); does. But if its anything like json_decode() then you should be all set.
This is my method of breaking down the object as you present here, its worth noting you may want to check counts, and see if the object is set first, or if the property exists in the object to prevent other means of the code breaking down the road. But what I have here is just pure example at its core, it will server its purpose though. But will not error handle which is why I say you may want to elaborate further on it doing those checks.
Anyway heres my code:
<?php
$str = '{"polygon":{"type":"Feature","properties":[],"geometry":{"type":"Polygon","coordinates":[[[-7302732.4720101,6527844.6333235],[-3193477.8319711,6606116.1502766],[-5111129.9973226,5001550.0527375],[-6637424.5779086,4884142.7773079],[-7772361.5737289,5158093.0866438],[-7302732.4720101,6527844.6333235]]]},"crs":{"type":"name","properties":{"name":"urn:ogc:def:crs:OGC:1.3:CRS84"}}}}';
$polygon = json_decode($str);
echo'<pre>';print_r($polygon);echo'</pre>';
$set = 1;
foreach($polygon->polygon->geometry->coordinates[0] as $coordinates)
{
echo 'Set '.$set.': ';$set++;
echo $coordinates[0].','.$coordinates[1].'<br>';
}
?>
see it in action http://7pz.net/geojson-parse.php (scroll to the bottom)
This should give you an array of all the coordinates and print them out line by line:
$string = '{"polygon":{"type":"Feature","properties":[],"geometry":{"type":"Polygon","coordinates":[[[-7302732.4720101,6527844.6333235],[-3193477.8319711,6606116.1502766],[-5111129.9973226,5001550.0527375],[-6637424.5779086,4884142.7773079],[-7772361.5737289,5158093.0866438],[-7302732.4720101,6527844.6333235]]]},"crs":{"type":"name","properties":{"name":"urn:ogc:def:crs:OGC:1.3:CRS84"}}}}';
$json = json_decode($string);
$coords_array = $json->polygon->geometry->coordinates[0];
foreach($coords_array as $c_a) {
echo $c_a[0] . "," .$c_a[1] . "<br>";
}
Access with:
$coords_array[0];
$coords_array[1];
$coords_array[2];
etc.
Basically you can turn the JSON string into an object and access each element with the -> notation.
I usally use a site called http://jsonviewer.stack.hu/ to decode JSON and find the path I need, then simply write them out as they appear, as in as in the above - $json->polygon->geometry->coordinates;.
Try it out yourself on the site.

Categories