Invalid key path in IndexedDB: restrictions? - javascript

I'm trying to create a really simple IndexedDB with some JavaScript, but it fails in the on handler already. Apparently the browser (Chrome 57) is not able to parse the keyPath (in Basic Concepts) of my storage.
I'm following more or less these simple examples: MDN or Opera-Dev.
Suppose I want to store objects like this one in the DB:
{
"1": 23, // the unique id
"2": 'Name',
"3": 'Description',
"4": null,
"5": null
}
Here is the code:
var sStoreNodes = 'nodes';
var sIdFieldNode = '1'; // the important part
// event is fired for creating the DB and upgrading the version
request.onupgradeneeded = function(event)
{
var db = event.target.result;
// Create an objectStore for nodes. Unique key should be the id of the node, on property 1.
// So ID will be the key!
var objectStore = db.createObjectStore(
sStoreNodes,
{
// changing to a plain string works, if it is a valid identifier and not just a strigified number
'keyPath' : [ sIdFieldNode ],
'autoIncrement' : false // really important here
});
};
The error message reads like:
Uncaught DOMException: Failed to execute 'createObjectStore' on 'IDBDatabase': The keyPath option is not a valid key path.
at IDBOpenDBRequest.CCapIndexedDB.request.onupgradeneeded
I can also try to leave out the key path, but I'm wondering why this happens and want can I do about it, if I really need to use a (complex) key path.
Regarding the spec:
I'm not sure, whether this can be applied here:
A value is said to be a valid key if it is one of the following ECMAScript [ECMA-262] types: Number primitive value, String primitive value, Date object, or Array object.
and what this actually means:
If the key path is a DOMString, the value [for getting the key path] will be a DOMString equal to the key path. If the key path is a sequence, the value will be a new Array, populated by appending Strings equal to each DOMString in the sequence.
Edit This works, if you don't use a stringified number, but a string instead, which is a valid identifier (beginning with a character [a-zA-Z]). So 'keyPath' : 'b' is OK. I guess this is because this value is used for creating paths like a.b.c.

Here is the definition of a key path, from the spec:
A key path is a DOMString or sequence that defines how to extract a key from a value. A valid key path is one of:
An empty DOMString.
An identifier, which is a DOMString matching the IdentifierName production from the ECMAScript Language Specification [ECMA-262].
A DOMString consisting of two or more identifiers separated by periods (ASCII character code 46).
A non-empty sequence containing only DOMStrings conforming to the above requirements.
For a string containing an integer, clearly the first, third, and fourth options do not apply. For the second, we have to see what an IdentifierName is, which is a little complicated, but basically it has to start with a letter, underscore, or dollar sign. This means that a string containing just an integer is not a valid key path.
If you really do have an object where the primary key is in a field whose name is a string containing an integer, you can either rename the field or not use key paths (in which case you have to manually specify the key as the second argument to IDBObjectStore.add and IDBObjectStore.put).
You linked to the definition for a key, which defines the valid values that a key can have (like for an object {a: 1} where the key path is 'a' the key is 1, which is valid).
The other thing you linked to is about key paths like a.b.c referencing {a: {b: {c: 1}}}.

Related

How to make JSON.parse() to treat all the Numbers as BigInt?

I have some numbers in json which overflow the Number type, so I want it to be bigint, but how?
{"foo":[[0],[64],[89],[97]],"bar":[[2323866757078990912,144636906343245838,441695983932742154,163402272522524744],[2477006750808014916,78818525534420994],[18577623609266200],[9008333127155712]]}
TLDR;
You may employ JSON.parse() reviver parameter
Detailed Solution
To control JSON.parse() behavior that way, you can make use of the second parameter of JSON.parse (reviver) - the function that pre-processes key-value pairs (and may potentially pass desired values to BigInt()).
Yet, the values recognized as numbers will still be coerced (the credit for pinpointing this issue goes to #YohanesGultom).
To get around this, you may enquote your big numbers (to turn them into strings) in your source JSON string, so that their values are preserved upon converting to bigint.
As long as you wish to convert to bigint only certain numbers, you would need to pick up appropriate criteria (e.g. to check whether the value exceeds Number.MAX_SAFE_INTEGER with Number.isSafeInteger(), as #PeterSeliger has suggested).
Thus, your problem may be solved with something, like this:
// source JSON string
const input = `{"foo":[[0],[64],[89],[97]],"bar":[[2323866757078990912,144636906343245838,441695983932742154,163402272522524744],[2477006750808014916,78818525534420994],[18577623609266200],[9008333127155712]]}`
// function that implements desired criteria
// to separate *big numbers* from *small* ones
//
// (works for input parameter num of type number/string)
const isBigNumber = num => !Number.isSafeInteger(+num)
// function that enquotes *big numbers* matching
// desired criteria into double quotes inside
// JSON string
//
// (function checking for *big numbers* may be
// passed as a second parameter for flexibility)
const enquoteBigNumber = (jsonString, bigNumChecker) =>
jsonString
.replaceAll(
/([:\s\[,]*)(\d+)([\s,\]]*)/g,
(matchingSubstr, prefix, bigNum, suffix) =>
bigNumChecker(bigNum)
? `${prefix}"${bigNum}"${suffix}`
: matchingSubstr
)
// parser that turns matching *big numbers* in
// source JSON string to bigint
const parseWithBigInt = (jsonString, bigNumChecker) =>
JSON.parse(
enquoteBigNumber(jsonString, bigNumChecker),
(key, value) =>
!isNaN(value) && bigNumChecker(value)
? BigInt(value)
: value
)
// resulting output
const output = parseWithBigInt(input, isBigNumber)
console.log("output.foo[1][0]: \n", output.foo[1][0], `(type: ${typeof output.foo[1][0]})`)
console.log("output.bar[0][0]: \n", output.bar[0][0].toString(), `(type: ${typeof output.bar[0][0]})`)
.as-console-wrapper{min-height: 100% !important;}
Note: you may find RegExp pattern to match strings of digits among JSON values not quite robust, so feel free to come up with yours (as mine was the quickest I managed to pick off the top of my head for demo purposes)
Note: you may still opt in for some library, as it was suggested by #YohanesGultom, yet adding 10k to your client bundle or 37k to your server-side dependencies (possibly, to docker image size) for that sole purpose may not be quite reasonable.

Is it possible to access the value of a Symbol in JavaScript?

I have been just introduced to the concept of Symbols in JavaScript. I have been informed that they can be used to create unique identifiers to avoid potential clashes.
For example...
let user = {
name : "John",
Symbol("id"): 123, // Symbol("id") is a unique identifier
Symbol("id"): 123 // and the Symbol("id") here is also different and unique
};
... I understand the above code. However, what is the actual identifier value of each "Symbol("id")"? How do I find out?
Any pointers appreciated.
No, you cannot see the "raw" value of the symbol in the JS environment, because it is implemented using the native C ++ code of the JS engine itself, and this implementation does not provide an opportunity to display it anywhere in the console or on a page.
You can think of symbols as big numbers and every time you create a symbol, a new random number gets generated (uuid). You can use that symbol (the random big number) as a key in objects.
Regarding this definition, you can look at a possible implementation of the symbol in ES5:
var Symbol;
if (!Symbol) {
Symbol = (function(Object){
// (C) WebReflection Mit Style License
var ObjectPrototype = Object.prototype,
defineProperty = Object.defineProperty,
prefix = '__simbol' + Math.random() + '__',
id = 0;
//... some other code
You can see that Math.random() is used here, so Symbol there will have long big number as a his main property (unique with high possibility).
Another way to explain what a symbol is is that a Symbol is just a piece of memory in which you can store some data. Each symbol will point to a different memory location. In the context of this definition, you can see the source code in C++ of the JS engine itself, for example V8 that is used in Chromium. If you know C++, you can try to find an implementation of Symbol() constructor there, but it won't be easy.
Therefore, we can say that a Symbol is a kind of unique memory area that a certain string describes. The memory area itself it's already term from a low-level programming, so you can think of it as something like 1010010011...
const id1 = Symbol("id");
const id2 = Symbol("id");
const user = {
name: "John",
[id1]: 123, // "[id1]" is a unique identifier
[id2]: 456, // and the value of "[id2]" here is also different
};
console.log('id1:', user[id1], id1.description);
console.log('id2:', user[id2], id2.description);
I wasn't able to get your question properly, I tried to help you hope this will work
let user = { // belongs to another code
name: "Alex"
};
let id = Symbol("id");
user[id] = 200;
alert( user[id] ); // we can access the data using the symbol as the key
From mdn:
Every symbol value returned from Symbol() is unique. A symbol value
may be used as an identifier for object properties; this is the data
type's only purpose.
console.log(Symbol('foo') === Symbol('foo'))
From an article in this answer:
ES6 symbols are similar to the more traditional symbols in languages
like Lisp and Ruby, but not so closely integrated into the language.
In Lisp, all identifiers are symbols. In JS, identifiers and most
property keys are still considered strings. Symbols are just an extra
option.
As mdn docs explains, you could access the Description that you passed but not the value of the Symbol:
Most values in JavaScript support implicit conversion to a string. For
instance, we can alert almost any value, and it will work. Symbols are
special. They don’t auto-convert.
For example,
let Sym = Symbol("Sym");
alert(Sym); // TypeError: Cannot convert a Symbol value to a string
That’s a "language guard" against messing up, because strings and
symbols are fundamentally different and should not occasionally
convert one into another.
If we really want to show a symbol, we need to call .toString() on it,
for example,
let Sym = Symbol("Sym");
alert(Sym.toString()); // Symbol(Sym), now it works
Or we can use get symbol.description property to get
the description on it, for example,
let _Sym = Symbol("Sym");
alert(_Sym.description); // Sym

How to set a Javascript Symbol as an Object Key?

I have a unique case where I need to use a Javacript symbol as an object's key. This is necessary because in order to conform to Sequelize's documentation, there are instances where we need to have something that looks like this:
const where = {
cost: {
[Op.gt]: 1000,
[Op.lt]: 2000
}
}
Both [Op.gt] and [Op.lt] are Javascript symbols that assist with querying. The block of code will query where a property called cost is greater than 1000 but less than 2000. But when I try to programmatically set the key/value pairs like:
where['cost'][[Op.gt]] = 1000;
I receive the following error:
Cannot convert a Symbol value to a string
This is a dynamic object, so I cannot hard code the symbols into the where query since the next user may not need to query by these parameters. How do I go about this? Thanks!
Remove 1 bracket around your symbol and you will be fine:
where['cost'][Op.gt] = 1000;
obj[Op.gt] means you're accessing an object property with the Op.gt name. obj[[Op.gt]] means you're accessing an object property with the name equal to an array [Op.gt] stringified. Which is similar to below:
const arr = [Op.gt];
const propertyName = arr.toString(); // => throw error "Cannot convert a Symbol value to a string"
where['cost'][propertyName];

JSON.stringify filter for properties with object values is not working

Can someone please clarify the rule of filtering?
property z is not being stringified properly, see last line pls.
MDN says "if an array, specifies the set of properties included in objects in the final string."
var obj = {x:1,y:'str',z:{a:1,b:2}};
var s = JSON.stringify(obj)
"{"x":1,"y":"str","z":{"a":1,"b":2}}"
var s = JSON.stringify(obj,["x","y","z"]);
"{"x":1,"y":"str","z":{}}" //z empty object why?
From MDN, if replacer(the second parameter of JSON.stringify) is an array it specifies the set of properties included in objects in the final string.
You set it as ["x","y","z"] in which case your resulting string has those three properties, what you may have missed is that it is applies to all properties not just those at the top level, so since you did not specify "a" and "b" in your array they where not included in the final string.
Try JSON.stringify(obj,["x","y","z", "a", "b"]); http://jsfiddle.net/mowglisanu/rhCTY/

Why should the "key" part in a JS hash/dict be a string?

In most JSON serializers/deserializers, the "key" part in a javascript dictionary/hash array is written as a string.
What is the benefit of using a string as the key as opposed to just typing the intended name in?
For example, say I define two objects k1 and k2 like so:
var k1 = { a: 1, b: 2, c: 3 }; // define name normally
var k2 = { "a": 1, "b": 2, "c": 3 }; // define name with a string
And I then ran the following tests:
alert(k1 == k2); // false (of course)
alert(k1.a == k2.a); // true
alert(k1["b"] == k2["b"]); // true
alert(uneval(k1)); // returns the k1 object literal notation.
alert(uneval(k2)); // returns the same string as above line.
alert(uneval(k1) == uneval(k2)); // true
So what's the point of having the keys be in double-quotation marks (a string) as in the way k2 was defined instead of just typing the key names in as in the way k1 was defined?
I just saw this over at Ajaxian pointing to Aaron Boodman's blog entry:
chromium.tabs.createTab({
"url": "http://www.google.com/",
"selected": true,
"tabIndex": 3
});
Since he also use camel case for tabIndex, I don't see any point in using a string at all.
Why not:
chromium.tabs.createTab({
url: "http://www.google.com/",
selected: true,
tabIndex: 3
});
Why would a JS ninja follows the convention of turning url, selected and tabIndex into a string?
Because JSON is a subset of the actual JavaScript literal syntax. For simplicity in implementing JSON parsers, double quotes are always required around strings, and as keys in JSON are strings, they are required there.
Not all legal JavaScript is legal JSON. While you can define object literals in JavaScript without the quotes, if you want interoperable JSON, you're going to need to put them in.
Because doing so, you avoid to use, by error, a javascript reserved keyword, like "do" for example. Using the string notation kept you on the safe side.
If the syntax diagram at json.org is to be believed, bareword property names are nonstandard. How many browsers did you run your tests on?
Apart from getting away from reserved keywords you can actually use whatever characters in your property names - including spaces, colons...
Not really sure why would you do that. I prefer using the normal "object" notation.

Categories

Resources