When I have a url like this:
http://server/site?firstname=Jack&lastname=Daniels
I understand that in my JavaScript a query for firstname should return "Jack".
But what should the query for firstname return in the following cases:
http://server/site?lastname=Daniels
http://server/site?firstname&lastname=Daniels
http://server/site?firstname=&lastname=Daniels
[Edit] To answer some of the comments: all of the above are legal querystrings, my question is not about how to retrieve the parameters but how to interpret them.
For the record, I parse querystrings with the following regular expression that covers all cases:
/([^?=&;]+)(?:=([^&;]*))?/g
Apparently there's a very popular question on how to retrieve querystring parameters, but the answer is incorrect (or at least not addressing edge cases).
[Update] My choice based on the answers from #Bergi and #zzzzBov:
http://server/site?lastname=Daniels => firstname: undefined
http://server/site?firstname&lastname=Daniels => firstname: true
http://server/site?firstname=&lastname=Daniels => firstname: ""
http://server/site?firstname=Jack&lastname=Daniels => firstname: "Jack"
A side effect is that I had to slightly modify my regex, as with the above rules the = sign needs to be captured:
/([^?=&;]+)(=[^&;]*)?/g
But what should the query for firstname return in the following cases:
http://server/site?lastname=Daniels
http://server/site?firstname&lastname=Daniels
http://server/site?firstname=&lastname=Daniels
Query strings can easily be represented by object notation in JavaScript. Simply set only the keys that are present in the query string on the object.
This means that for ?lastname=Daniels the object produced should be:
{
lastname: 'Daniels'
}
In cases where the key is present, but no value is given (no equals sign), the key should be set with a value of null which represents "no value".
This means that for ?firstname&lastname=Daniels the object produced should be:
{
firstname: null,
lastname: 'Daniels'
}
In cases where the key is present, and the value provided is empty (equals sign), the value is actually the empty string.
This means that for ?firstname=&lastname=Daniels the object produced should be:
{
firstname: '',
lastname: 'Daniels'
}
There is also an often overlooked case of where the same key is used multiple times. In the traditional query-string syntax (not PHP), keys can be used multiple times, as-is, to represent an array.
This means that for ?foo=bar&foo=baz the object produced should be:
{
foo: [
'bar',
'baz'
]
}
<aside>
In PHP land, keys used for arrays are suffixed with []:
`?foo[]=bar&foo[]=baz`
PHP will automatically convert the query-string server side such that:
$_GET['foo'] = array('bar', 'baz')
</aside>
Going back to the original question, what this implies is the following:
?lastname=Daniels has a value of undefined for the firstname key.
?firstname&lastname=Daniels has a value of null for the firstname key.
?firstname=&lastname=Daniels has a value of '' for the firstname key.
For cases where a key is used as a boolean based on its presence, you should be checking whether the object has the key set (and completely ignore the value, as it doesn't matter).
Typically this is done with obj.hasOwnProperty('key'), however as the object is really more of a hash, Object.prototype.hasOwnProperty.call(obj, 'key') is preferrable, which could be abstracted into a has function:
has(obj, key) {
return Object.prototype.hasOwnProperty.call(obj, key);
}
If you're using underscore.js, then you could use _.has(obj, key)
But what should the query return
That's your decision, what do you need? There are several querystring-evaluating functions of different complexity. Some of them can deal with repeated, nested, bracket-syntax values, others not. Famous ones can be found in How can I get query string values in JavaScript? and its duplicate questions.
However, you asked for conventions:
http://server/site?lastname=Daniels
The result should be something falsy to represent the absence of firstName. You might choose null or undefined.
http://server/site?firstname&lastname=Daniels
This often represents some boolean parameter, so returning true would be legitimate. Yet I'm sure there are libs that completely ignore this format.
http://server/site?firstname=&lastname=Daniels
That's quite obviously the empty string "".
Related
Is it possible to remove the quotes from keys in JSON.stringify? Normally it will have quotes:
const object = { name: "Foo Bar", birthdate: { date: "2000-01-01", time: "12:34" } };
console.log(JSON.stringify(object, null, " "));
Output:
{
"name": "Foo Bar",
"birthdate": {
"date": "2000-01-01",
"time": "12:34"
}
}
What I want is something like this:
{
name: "Foo Bar",
birthdate: {
date: "2000-01-01",
time: "12:34"
}
}
Is this even possible, or do I have to create my own JSON serializer?
It sounds like you are looking for a data-serialization format that is human-readable and version-control-friendly but not as strict about quotes as JSON.
Such formats include:
Relaxed JSON (RJSON) (simple keys and simple values generally do not require quotes)
Hjson (simple keys and simple values generally do not require quotes)
YAML (keys and values generally do not require quotes)
JavaScript object literal (also printed out by many implementations of "console.dir()" when passed a JavaScript object; simple keys generally not required to be quoted, but string values must be quoted by either single quotes or double quotes)
for completeness:
JSON (requires double-quotes around keys, also called property names, and requires double-quotes around string data values).
I've used the below NPM package to achieve this.
https://www.npmjs.com/package/stringify-object
Yes, it is possible to remove the quotes from the keys. Doing so will render it invalid as JSON, but still valid when used directly in JavaScript code, only if the keys you use are also valid variable names. When you paste a JSON object in JavaScript code, it may be useful to have the quotes removed, as they can in some cases take up a lot of data. If that's relevant to your project, then this is the answer for you.
The function below works for any JavaScript variable and works fine with objects/arrays containing objects/arrays. I've added comments to explain what's going on. Please note that this code does not check for possible reserved keywords that may not be allowed as JavaScript keys, such as "for".
function toUnquotedJSON(param){ // Implemented by Frostbolt Games 2022
if(Array.isArray(param)){ // In case of an array, recursively call our function on each element.
let results = [];
for(let elem of param){
results.push(toUnquotedJSON(elem));
}
return "[" + results.join(",") + "]";
}
else if(typeof param === "object"){ // In case of an object, loop over its keys and only add quotes around keys that aren't valid JavaScript variable names. Recursively call our function on each value.
let props = Object
.keys(param)
.map(function(key){
// A valid JavaScript variable name starts with a dollar sign (?), underscore (_) or letter (a-zA-Z), followed by zero or more dollar signs, underscores or alphanumeric (a-zA-Z\d) characters.
if(key.match(/^[a-zA-Z_$][a-zA-Z\d_$]*$/) === null) // If the key isn't a valid JavaScript variable name, we need to add quotes.
return `"${key}":${toUnquotedJSON(param[key])}`;
else
return `${key}:${toUnquotedJSON(param[key])}`;
})
.join(",");
return `{${props}}`;
}
else{ // For every other value, simply use the native JSON.stringify() function.
return JSON.stringify(param);
}
}
A JSON string is a string type basically, a common language that Servers understand, that's why we need to send JSON to the Servers for further processing. What you are trying to get is an object in javascript.
But you already have that object to work with.
const object = { name: "Foo Bar", birthdate: { date: "2000-01-01", time: "12:34" } };
I found this notation in a javascript code:
this.numbers[""].x
I can't understand what the apices mean. What do they do?
It means that numbers is an object which contains a key which is the empty string. Using [''] will access the property of the object which is the empty string (and then .x will access the x property inside it).
It sounds very strange, and it is, but it's syntactically legal to construct such a thing:
const numbers = {
'': {
x: 'valueOfX'
}
};
console.log(numbers[''].x);
(If you ever see this sort of thing in code that you have control over, I'd suggest considering refactoring it to be less confusing)
I came across this example in a MDN doc, for example:
class Search1 {
constructor(value) {
this.value = value;
}
[Symbol.search](string) {
return string.indexOf(this.value);
}
}
If I pull up node, and run just the line included as part of the object literal, it doesn't work:
> Symbol.search
Symbol(Symbol.search)
> [Symbol.search]
[ Symbol(Symbol.search) ]
> [Symbol.search]('somthing')
TypeError: [Symbol.search] is not a function
I think I've also seen this syntax in a few other places, like e.g. in the react docs:
handleChange(event) {
this.setState({ [event.target.id]: event.target.value });
}
Is this just a use of destructuring syntax? It doesn't seem like it.
brackets are used when you have variable as key and not a plain string.
const obj = {
"someId": 'abc',
};
const e = {
target: {
id: "someId"
}
};
console.log(obj[e.target.id]);
Apart from above mentioned, it is also used to access the numeric keys (Just like array) and when key is computed. See - https://javascript.info/object#square-brackets
Turns out, that's just part of the spec.
It looks a bit like array de-structuring, but it's not.
In the case of [event.target.id], you're assigning the value that event.target.id points to be a key in the object passed to setState(). If you tried to do this without the brackets ([]), it would not work, not how you expect anyway.
In the case of [Symbol.search](string), here you're using the Symbol.search symbol (see symbols) as a key which is dynamically evaluated immediately to its actual, unique value. The dynamic evaluation is allowed because this value becomes the key in an object literal definition. The value which the key points to is a function being defined here, which takes string as its first and only parameter, and operates on that. This is a hook for allowing an object to define how it behaves when used as a parameter, in this case to the .search() function. See here.
Thanks for #randomSoul's answer, for completing it I might say that braces also make you to have a string key with spaces like below:
const myOBJ = {
'my key': 'my assigned String Value'
}
Then you can call that key value pair with this braces syntax like:
console.log(myOBJ['my key'])
This is rarely used in JavaScript, but the main purpose of using braces for getting the value from object literal is for getting dynamically computed keys of object. Like that you have an object that each key is represented user id, and you based on that you want to decide to get the specific user id that you got from your url params or somewhere else then you would be able to get user data like below:
console.log(lastFiveUserData[myUserId].age)
Updated Question
What, exactly, qualifies as a valid property name in Javascript? How do various methods of property assignment differ? And how does the property name affect property access?
Note
The answers to my original question (seen below) helped to clear some things up, but also opened a new can of worms. Now that I've had a chance to become a bit more familiar with JavaScript, I believe I've been able to figure a lot of it out.
Since I had a hard time finding this information consolidated into one explanation, I thought it might be helpful to expand my original question, and attempt to answer it.
Original Question
Originally, there was some confusion with the MDN JavaScript guide (object literals). Specifically, I wondered why they claimed that if a property name was not a valid JavaScript identifier, then it would have to be enclosed in quotes. Yet, they offered example code that showed that the number 7 could be used — without quotes — as a property name.
As it turns out, the guide simply left off one important part, and Pointy updated it (changes in bold):
If the property name would not be a valid JavaScript identifier or number, it must be enclosed in quotes.
I also wondered why property names were allowed to deviate away from the "may not start with a digit" rule, that applies to identifiers. That question actually reveals the complete misunderstanding that I had of property names, and is what lead me to do some more research.
Answer for 1st question:
Yes, the statement given in the MDN guide is not 100% accurate, but in your daily work it'd be better to follow it as rule. You really don't need to create properties names which are numbers.
Answer for 2nd question:
A property name may not start with a digit but a property name that is a number without any other characters in its name is fine.
This exception exists because the properties with number for name as the same as indexes.
Let's try this:
var obj = {7: "abc"};
obj[7]; // works fine
obj.7; // gives an error (SyntaxError)
Now try to call Array.push on the object and observe what happens:
Array.prototype.push.call(obj, "xyz");
console.log(obj);
console.log(obj[0]);
// Prints
Object {0: "xyz", 7: "abc", length: 1}
"xyz"
You can see that few new properties (one with name 0 and another with name length) have been added to the object. Moreover, you can use the object as an array:
var obj = { "0": "abc", "1": "xyz", length: 2 };
Array.prototype.pop.call(obj); // Returns: "xyz"
Array.prototype.pop.call(obj); // Returns: "abc"
You can use array's methods on objects and this is called Duck Typing.
Arrays are nothing more than objects with some predefined methods.
From MDN:
Array elements are object properties in the same way that length is a property, but trying to access an element of an array with dot notation throws a syntax error, because the property name is not valid. There is nothing special about JavaScript arrays and the properties that cause this. JavaScript properties that begin with a digit cannot be referenced with dot notation and must be accessed using bracket notation.
Now you can understand why a number for property name is valid. These are called just indexes and they are used in JavaScript arrays. And since JavaScript needs to be consistent with other languages, numbers are valid for indexes/properties names.
Hope this makes it clear.
Here are some interesting articles:
JavaScript identifiers (in ECMAScript 5)
JavaScript identifiers (in ECMAScript 6)
Short Answer
Object property names can be any valid identifier, numeric literal, or string literal (including the empty string).
With that said, there are some potentially confusing intricacies to keep in mind about JavaScript property names, as described below.
And unless you're working with valid (non-negative integer) array indexes, it's a good idea to explicitly assign all numerical property names as strings.
Negative Numbers
What might look like a negative number is actually an expression — something property names do not support.
// SyntaxError
const obj = { -12: 'nope' };
Fortunately, bracket notation handles expressions for us.
// Successful property assignment.
const obj = {};
obj[-12] = 'yup';
Typecasting
All property names are typecasted into strings before being stored.
const obj = {
12: '12'
};
console.log(typeof Object.keys(obj)[0]); // -> string
Parsing
But even before typecasting occurs, keys are parsed according to the syntax used, and transformed into a decimal literal.
const obj = {
// Valid string literal
'022': '022',
// Interpreted as decimal
6: '6',
// Interpreted as floating-point
.345: '0.345',
// Interpreted as floating-point
1.000: '1',
// Interpreted as floating-point
8.9890: '8.989',
// Interpreted as decimal
000888: '888',
// Interpreted as octal
0777: '511',
// Interpreted as hexadecimal
0x00111: '273',
// Interpreted as binary
0b0011: '3',
};
/* Quoted property name */
console.log(obj['022']); // "022"; as expected
console.log(obj[022]); // undefined; 022 is an octal literal that evaluates to 18 before our lookup ever occurs
/* Valid (non-negative integer) array index */
console.log(obj[6]); // "6"; as expected
console.log(obj['6']); // "6"; as expected
/* Non-valid array index */
console.log(obj[0x00111]); // "273"; we're accessing the property name as it was assigned (before it was parsed and typecasted)
console.log(obj['0x00111']); // undefined; after parsing and typecasting, our property name seems to have disappeared
console.log(obj['273']); // "273"; there it is, we found it using the evaluation of our original assignment
I know that in most programming scenarios, the preference is for empty collections to null collections when there are 0 elements. However, most languages that consume JSON (like JavaScript) will treat empty lists/objects as true and null ones as false. For example this would be both true and an object in JavaScript:
{
"items_in_stock": {"widgets":10, "gadgets": 5}
}
But this is also true:
{
"items_in_stock": {}
}
And this is false:
{
"items_in_stock": null
}
Is there a convention on empty objects/lists for JSON? And what about for numbers, booleans, and strings?
It is good programming practice to return an empty array [] if the expected return type is an array. This makes sure that the receiver of the json can treat the value as an array immediately without having to first check for null. It's the same way with empty objects using open-closed braces {}.
Strings, Booleans and integers do not have an 'empty' form, so there it is okay to use null values.
This is also addressed in Joshua Blochs excellent book "Effective Java". There he describes some very good generic programming practices (often applicable to other programming langages as well). Returning empty collections instead of nulls is one of them.
Here's a link to that part of his book:
http://jtechies.blogspot.nl/2012/07/item-43-return-empty-arrays-or.html
"JSON has a special value called null which can be set on any type of data including arrays, objects, number and boolean types."
"The JSON empty concept applies for arrays and objects...Data object does not have a concept of empty lists. Hence, no action is taken on the data object for those properties."
Here is my source.
There is the question whether we want to differentiate between cases:
"phone" : "" = the value is empty
"phone" : null = the value for "phone" was not set yet
If we want differentiate I would use null for this. Otherwise we would need to add a new field like "isAssigned" or so. This is an old Database issue.
Empty array for empty collections and null for everything else.