Finding the length of an associative array encoded by PHP - php

I have a multidiminsional array that I have created in php that is passed back to a jQuery script. I need to iterate through this array and process the data.
In Firebug I can see that the data is located at data.data.items. I've tried finding the length of the array using data.data.items.length, but it comes back as undefined. Interestingly, this worked prior to my php portion working correctly when it passed back an array of 8 empty items. Now that it's populated (and the indexes are strings), length doesn't work. There is also an object in each of the items. What's breaking this?

An Array in JavaScript is an object nonetheless. When setting values using strings (or anything that isn't an integer), you are actually setting a property of the object (you are actually doing this when setting it with integer keys as well, but it's handled slightly differently).
To the issue of its sudden breakage after using strings as keys, I would expect that PHP realizes when you have an honest-to-goodness array versus an associative array, thus it sends arrays (surrounded by []) when all keys are integers, and objects (surrounded by {}) otherwise. I believe in the string-keyed case, PHP is generating objects, and thus .length becomes undefined (rather than 0 as in an empty array).
To answer your question, there is a simple way to count the "length" of this data:
var i = 0;
for (var item in data.data.items) {
i++;
}
Which will iterate through each property of data.data.items and count them. Note that if you (or any library you include) adds a property to the Object prototype, this will not produce expected results. This is fairly uncommon, but you must be aware of it when using for..in.
You can address this by using the method Nagh suggested, which ignores properties not defined on that particular object:
var i = 0;
for (var item in data.data.items) {
if(data.data.items.hasOwnProperty(item)) {
i++;
}
}

You can always use "foreach" kind of loop. In this case, you don't need to know what array length is there or even is it array or not, since you can iterate over object properties aswell.
As Joe already has pointed, javascript doesn't have associative arrays and when you trying to use one - you end up with object with properties. However, if u sure, that only properties this object got - is your array you can use code like that:
for (i in arr) {
if (arr.hasOwnProperty(i)) {
//do something with arr[i]
}
}
However if you really need an array, consider using integer as an array index.

Related

How to return an array instead of object in JSON response?

I've one working REST API developed using Slim PHP framework.
It's working absolutely fine.
The only issue is when there is no error present i.e. an array $errors is empty it comes as an array in JSON response but when the array $errors contains any error like $errors['user_name'] then it comes as an object in JSON response.
Actually I want to return the array when error is present. How should I do this? Can someone please help me in this regard?
Thanks in advance.
When your $errors is not empty, pass it through json_encode and echo it.
It will give you JSON object in return,
then convert JSON object into JavaScript array. (see the following code.)
var o = {"0":"1","1":"2","2":"3","3":"4"}; // your response object here
var arr = Object.keys(o).map(function(k) { return o[k] });
console.log(arr);
Recently got a close issue with Symfony's JsonResponse::create()
Turns out arrays with index not starting at 0 will be encoded into objects, as well as arrays with "holes", and probably any array with at least one non-int key.
In other word, arrays with anything else than consecutive numeric keys starting from index 0 seem to be encoded as object.
I guess this is designed to avoid sending big empty arrays when you map a handful of datas with big indices like [14334, 839493, 246193], and is probably documented somewhere.
Learning if this is a Symfony of json_encode behavior from comments would be welcomed addition :)
Note : Even if you return an array, it seems necessary to wrap it in an object for GET request to prevent some XSSI and JSON-JavaScript Hijacking.

Iterate only through objects

I am receiving some JSON from PHP like this (previewed in Firefox tools):
1:Object
0:Object
1:Object
2:Object
3:Object
Name: "SomeName"
I now want to iterate through the objects, but not the Name key. It seems if I do an $.each it includes the key/value pair. How can I avoid it? Also why can I choose a numerical value for the first Object (I have it "1") but not assign a value to it? I wish it could look like this for example
1:SomeNameIGaveIt
0:Object
1:Object
2:Object
3:Object
That would make my Name k/v pair obsolete.
JSON
{"1": {"Name":"SomeName", "0":{"data":"data"}. "1":{"data":"data"}}}
In JavaScript, a String is a "subclass" of Object. Lucky for you, there's the typeof operator;
in the Chrome console
var a = "foo" //makes a String
var b = {} //makes an empty Object
typeof a
"string"
typeof b
"object"
Using this, you can check to ensure something is not a string before doing an operation on it.
Other option: instead of iterating using the json $.each call, you could just iterate over it via a JavaScript for-loop. If you know the number of elements inside your SomeNameIGaveIt object is fixed, you could use a fixed number of iterations and then use the array indexing operator (SomeNameIGaveIt[index]), or use a for-in loop (for (var i in array){...})
If you use a for-in loop, you can check if the index is a number using typeof as mentioned above. Any of these approaches is going to yield pretty similar results, so pick whatever suits you best.

Why is my php array being saved to MongoDB as an object, then being retrieved as an associative array with stringified keys?

Background:
I have a class in this application I'm building whose job is:
__construct: $this->data = $this->mongoDB->collection->findOne();
Intermediate functions are employed to manipulate the data in tens of different ways each request. One manipulation could trigger one which would trigger another. This allows me to do unlimited updates to the mongo document with just one query, as long as $this->data['_id'] remains the same. This is the only place where data manipulation of this specific collection is allowed.
__destruct: $this->monboDB->collection->save($data)
Data is then read back, json_encode'd and sent to Javascript to draw the page
Intention:
I intended to delete a member of an array by looping through said array, matching a value within it, and unsetting that. Example:
foreach($this->data['documents'] as $key => $val){
if($val == $toBeDeleted){
unset($this->data['documents'][$key];
}
}
Then, this would be saved to the DB when the script finishes.
Problem:
When javascript reads back the data, rather than having ['a', 'b', 'd'], I had {'0': 'a', '1': 'b', '3': 'd'} - which can't be treated like an array and would pretty much break things.
I had this question half typed out before my a-hah! moment, so I figured I'd post my own answer to it too for future reference.
In php, an associative array and an array are all the same. You can have out of order keys, nonconsecutive keys, and almost any key that you'd like to use in calling your array member. Most, if not all, php array functions work with any array key. Objects are a totally different thing.
That being said, Javascript doesn't share the same rules for arrays. A javascript array must have consecutive keys starting at zero, otherwise it is an object. MongoDB is similar to Javascript in this way.
When php converts an object to be used in MongoDB or in Javascript, if the php array doesn't follow that rule, it becomes a Javascript object.
The problem was after unsetting an array index, it left a gap, causing nonconsecutive array keys, causing it to become an object. Simple fix would either be array_slice($array, $key, 1) or $array = array_values($array)

Unify variable types of array elements

After hours of debugging, I found an error in one of my scripts. For saving different event types in a database, I have an array of unique data for each event that can be used to identify the event.
So I basically have some code like
$key = md5(json_encode($data));
to generate a unique key for each event.
Now, in some cases, a value in the $data array is an integer, sometimes a string (depending on where it comes from - database or URL). That causes the outputs of json_encode() to be different from each other, though - once including quotes, once not.
Does anybody know a way to "unify" the variable types in the $data array? That would probably mean converting all strings that only contain an integer value to integer. Anything else I have to take care of when using json_encode()?
array_walk_recursive combined with a function you have written to the effect of maybe_intval which performs the conversion you talk about on a single element.
EDIT: having read the documentation for array_walk_recursive more closely you'll actually want to write your own recursive function
function to_json($obj){
if(is_object($obj))
$obj=(array)$obj;
if(is_array($obj))
return array_map('to_json',$obj);
return "$obj"; // or return is_int($obj)?intval($obj):$obj;
}

Will the Order of my Associative Array be maintained from PHP to Javascript?

In PHP I'm running a mysql_query that has an ORDER BY clause. I'm then iterating through the results to build an associative array, with the row_id as the key.
Then, I'm calling json_encode on that array and outputting the result.
This page is loaded with AJAX, and defined in a Javascript variable. When I iterate through that Javascript variable, will I still have the order that was returned from the mysql_query?
PHP arrays are somewhat unique in their property of maintaining insertion order. Javascript doesn't have associative arrays per se. It has objects, which are often used as associative arrays. These do not guarantee any particular key order.
Why not output them as an array? That will have a particular order. If you want some sort of key lookup why does the order matter?
What cletus says is correct, but in my experience, most browsers will maintain the order. That being said, you should consider using an Array. If you need to sort it once you receive it on the client-side, just use the .sort() function in JavaScript:
rows.sort(function(a, b) {
return a.row_id - b.row_id;
}
Though it seems like it works, the order of properties in an object can't be counted on. See the many comments below for more info (smarter eyes than mine). However, this was the code I used to test the behavior in my own limited testing:
var test = {
one: 'blah',
two: 'foo',
another: 'bar'
};
for (prop in test) {
document.write(prop + "<br />");
}
Prints (in Firefox 3.6.3 and Chrome 5.0.375.9):
one
two
another
Also, you may want to be sure you're getting the type of JSON encoding you're needing back from json_encode(), such as an object (uses {} curly braces) and not an array ([] braces). You may need to pass JSON_FORCE_OBJECT to json_encode() to force it.
Edited to clarify that the Array approach is preferred)
Edited again (sorry), as I had overlooked pcorcoran's comment, which has a link to an issue in Chromium's issue tracker regarding this. Suffice to say, the order an object's properties is not reliable.

Categories