Is it possible to have JavaScript object evaluate to a primitive - javascript

I would like to be able to define an object that will evaluate to a primitive but also may have attached functions. Effectively the same way the "String" class works. Is there a way for me to extend the class without modifying the String prototype.
One use case for me would be like this:
class ID extends String{
constructor(len){
let value = 'Very unique id';
this.len = len;
this = value; //Or some other way of assigning the objects value
}
someRandomFunction(){
console.log('I was called');
}
}
const newId = new ID(10);
console.log(newId) // prints "Very unique id"
newId.someRandomFunction() // prints "I was called"
console.log(newId.len) // prints "10"

Most situations where this will be relevant, you will be implicitly calling newId[Symbol.toPrimitive](). You can define this function on your object, which will result in being able to precisely define how your object behaves when you attempt to use it as different things.
In your case, it's a wrapper object around a string value, right? You could simply return the string for all calls to toPrimitive, and thus any attempt to use your object as anything other than an object will result in the operation being performed on the backing string instead. Here's an example:
class ID {
constructor(len){
this.value = 'Very unique id';
this.len = len;
}
[Symbol.toPrimitive](hint){
return this.value;
}
someRandomFunction(){
console.log('I was called');
}
}
const newId = new ID(10);
console.log('' + newId) // prints "Very unique id"
newId.someRandomFunction() // prints "I was called"
console.log(newId.len) // prints "10"
Note that console.log is a bit of a finicky function: it checks the type of the argument (which you cannot change) and if it's an object, it won't coerce it to anything but run some code to inspect the object's properties. So console.log(newId) you'll never get to have a satisfying custom output (unless you're in node.js). It will always print like an object. However, any attempt to coerce the object to a primitive, as in '' + newId will result in your desired behavior.

Related

Print JSON path and variable result in one call in JavaScript

Is it possible to console.log something like this:
myParent.myChildData(5)
(variable literal name + value in brackets)
from a JSON object such as this:
{myParent: {myChildData: 5}}
I would like to do it with referencing the object notation ideally only once. Something like:
console.log(printExpression(myParent.myChildData))
Where printExpression I'm certainly happy to be a generic helper function that could return this. I've searched high and low, but obviously printExpression receives the actual evaluated value and this causes a road block.
You can turn JSON into a JavaScript object by using JSON.parse(jsonString).
You can store that as a variable and then console.log it.
Or you can just directly console.log the passed data like this:
console.log(JSON.parse('{"myparent":{"myChildData": 5}}').myParent.myChildData);
Edit
After understanding what exactly the helper function does, I've created a printExpression function that returns string values based on your example.
function printExpression(object, stringBefore) {
//Recursively make objects with keys as methods
let newObject = {};
for (var key in object) {
//Make sure the key exists on the object
if (object.hasOwnProperty(key)) {
let value = object[key];
//If the value is an object, just add a get method that returns the object
if (typeof(value) == "object") {
let childObject = printExpression(value, key + ".");
newObject[key] = childObject;
}
//If not, make a method that returns the wanted syntax
else {
//Form the string based on specific syntax
let str = key + "(" + value + ")";
//Check if we should add stringBefore
if (stringBefore) {
str = stringBefore + str;
}
newObject[key] = str;
}
}
}
//Return the new object
return newObject;
}
var example = printExpression(JSON.parse('{"myParent": {"myChildData": 5}}'));
console.log(example.myParent.myChildData);
How It Works
When creating the helper object, it recursively reads all the keys of the original object and makes a new object that returns the keys in an organized way. For example if the original object was { greeting: "hello" } then newObject.greeting would be "greeting(hello)" (as you said it should be).
Possible Problems
Doesn't get updated when you change the original object. I don't think this will be much of a problem as you seem to be reading static JSON data, but just letting you know.

Can you create object property names using template literals in javascript?

I'm attempting to write a function that takes an array of Strings as its input. The function should return an object with (1) key, value pair. The first element of the array should be the property name and the last element of the array should be its key.
function transformFirstAndLast(array){
return {`${array[0]}`: array[length-1];}
}
The above gives me an error. Can someone provide a detailed explanation why this isn't working? I'd like to avoid creating separate variables to store the values from the first and last array indices.
Your question really boils down to, "Can I use expressions as keys in object literals?"
The answer is yes (since es6):
function yell(template, ...parts) {
return parts[0] + '!';
}
function foo() {
return 'bar';
}
class Person {
constructor(first, last) {
this.first = first;
this.last = last;
}
toString() {
return `${this.first} ${this.last}`;
}
}
let names = ['Abe'];
let my_obj = {
[3+5]: 'some_value',
[yell `${foo()}`]: foo(),
[names[0]]: 64,
[new Person('Rafael', 'Cepeda')]: 25
};
console.log(my_obj);
As long as the expression evaluates to a string, all is fair.
You are missing the { and you can't use a template strings as a key. To use a "variable" as a key in an object, you should use the brakets around the variable. Here's the working code.
function transformFirstAndLast(array){
return {[array[0]]: array[array.length-1]};
}
I guess template literal is unnecessary here simply try like this way after fixing this line array[length-1] because its wrong, correct one is array[array.length-1]. I've added more verbose code but you can also do the shorthand version like return {[array[0]]: array[array.length-1]}; on your transformFirstAndLast(array) function.
function transformFirstAndLast(array){
const result = {};
result[array[0]] = array[array.length-1];
return result;
}
console.log(transformFirstAndLast(['a','b','c','d','e']))
yes you can use string literals as key. But the thing is that they are calculated in the run time. So you need to treat them as expressions. And to have variables/expressions as your keys you need to wrap them inside []
let a = {
`key`: value
} // is not allowed
let a = {
[`key`]: value
} // is allowed since you have wrapp
for your case
return {[`${array[0]}`]: array[array.length-1]};
Now since you have wrapped the array[0] item inside a string literal, you will get string values for your zero'th item. If your array[0] is to be a object this would not work as well. It needs to be either string or number. Or else you would get "[object Object]" as your key
var input = ["name", "fname", "lname", "stackOverflow"];
function transformFirstAndLast(array){
return {[array[0]]: array.pop()}
}
responseObject = transformFirstAndLast(input)
console.log(responseObject)

Why does 'this' inside String.prototype refer to an object type, not a string type?

I'm trying to extend string to provide a hash of itself. I am using the Node.js crypto library.
I extend string like this:
String.prototype.hashCode = function() {
return getHash(this);
};
and I have a getHash function that looks like this:
function getHash(testString) {
console.log("type is" + typeof(testString));
var crypto = require('crypto');
var hash = crypto.createHash("sha256");
hash.update(testString);
var result = hash.digest('hex');
return result;
}
The function works fine when called directly, as in
var s = "Hello world";
console.log(getHash(s));
but when I try:
var s = "ABCDE";
console.log(s.hashCode());
the method call fails. It appears that this in the String.prototype.hashCode is identified as an object when crypto.hash.update is called, but a string is expected. I thought that this inside String.prototype would be the string itself, but for some reason it looks like an object to getHash(). How can I fix it?
this can’t be of a primitive type outside of strict mode, so it becomes a String wrapper type, which doesn’t behave like a primitive string at all (particularly as far as typeof and equality – both strict and loose – go). You can cast it:
String.prototype.hashCode = function () {
return getHash('' + this);
};
where '' + is used to cast any value to a primitive string. (String(this) also works, if you feel that it’s clearer.)
You can also go into strict mode, where things just make sense:
String.prototype.hashCode = function () {
'use strict';
return getHash(this);
};
When you call a method on a variable of primitive type, so-called auto-boxing is taken place. That process wraps a primitive value into corresponding object, for example 'asdf' to new String('asdf'). Because technically primitive values don't have methods and properties, they are hosted in object prototypes. With auto-boxing you could call methods on primitive values. And within a method, this is always the object that has that method.
If you want to access the primitive value in a method, you could either pass it as an argument, or as you would like, retrieve primitive value from this. For example:
var str = new String('asdf') // String {0: "a", 1: "s", 2: "d", 3: "f", length: 4, formatUnicorn: function, truncate: function, splitOnLast: function, [[PrimitiveValue]]: "asdf"}
String(str) // 'asdf'
var num = new Number(123) // Number {[[PrimitiveValue]]: 123}
Number(num) // 123
var bool = new Boolean(true) // Boolean {[[PrimitiveValue]]: true}
Boolean(bool) // true

In Javascript, why does array.map(String) return array of strings?

The list processing routine map on an array object is very convenient at times. Here's one of the handy ways to use it:
var numarr = [1,2,3,4];
console.log(numarr.map(String))
>>> ["1", "2", "3", "4"]
I took this for granted thus far. Today I was however puzzled by it. What the map function is returning above is an array of strings. We typically pass a function to map as argument. In above case we pass String object. String is implemented inside the Javascript implementation, so I don't know what kind of specialities it has. The above code works as if a new instance of String is created for each item in array.
If it's not clear, consider this. If I decide to implement an object in Javascript say MyString and pass it to map, I won't get the above behavior.
function MyString(x) { this.val = x; }
MyString.prototype.toString = function () { return String(this.val); };
var ms = new MyString(4)
console.log(String(ms));
>>> "4"
var arr = [1,2,3];
arr.map(MyString)
>>> [undefined, undefined, undefined]
Does anyone know why then arr.map(String) works the way it does?
Update: A comment I added below clarifies my question better.
At the end of the 2nd snippet, try console.log(val). You'll notice you've leaked a global:
var arr = [1,2,3];
arr.map(MyString);
console.log(val); // "3"
When using arr.map(MyString), you're calling that constructor as a function, without the new to create instances. And, since MyString doesn't return anything, you get undefined in the results. But, you've still set this.val, while this isn't an instance but is rather the global object.
String doesn't return undefined because it has a return when called without new:
When String is called as a function rather than as a constructor, it performs a type conversion.
Returns a String value (not a String object) computed by ToString(value). If value is not supplied, the empty String "" is returned.
You can imitate this with MyString by checking if this is an instance first, returning a new instance when this isn't one already:
function MyString(x) {
if (this instanceof MyString) {
this.val = x;
} else {
return new MyString(x);
}
}
var arr = [1, 2, 3];
arr.map(MyString); // [ {val: "1"}, {val: "2"}, {val: "3"} ]
Array.map returns an array whose elements are the value returned by applying the specified function to each value in the this array. String is a function; it returns a string. That's all there is to it.
Thats is because String is a function. It returns a string constructed from what is passed to it. For example, if you call String(100), it will return "100".

What is this javascript code doing?

this.String = {
Get : function (val) {
return function() {
return val;
}
}
};
What is the ':' doing?
this.String = {} specifies an object. Get is a property of that object. In javascript, object properties and their values are separated by a colon ':'.
So, per the example, you would call the function like this
this.String.Get('some string');
More examples:
var foo = {
bar : 'foobar',
other : {
a : 'wowza'
}
}
alert(foo.bar); //alerts 'foobar'
alert(foo.other.a) //alerts 'wowza'
Others have already explained what this code does. It creates an object (called this.String) that contains a single function (called Get). I'd like to explain when you could use this function.
This function can be useful in cases where you need a higher order function (that is a function that expects another function as its argument).
Say you have a function that does something to each element of an Array, lets call it map. You could use this function like so:
function inc (x)
{
return x + 1;
}
var arr = [1, 2, 3];
var newArr = arr.map(inc);
What the map function will do, is create a new array containing the values [2, 3, 4]. It will do this by calling the function inc with each element of the array.
Now, if you use this method a lot, you might continuously be calling map with all sorts of arguments:
arr.map(inc); // to increase each element
arr.map(even); // to create a list of booleans (even or odd)
arr.map(toString); // to create a list of strings
If for some reason you'd want to replace the entire array with the same string (but keeping the array of the same size), you could call it like so:
arr.map(this.String.Get("my String"));
This will create a new array of the same size as arr, but just containing the string "my String" over and over again.
Note that in some languages, this function is predefined and called const or constant (since it will always return the same value, each time you call it, no matter what its arguments are).
Now, if you think that this example isn't very useful, I would agree with you. But there are cases, when programming with higher order functions, when this technique is used.
For example, it can be useful if you have a tree you want to 'clear' of its values but keep the structure of the tree. You could do tree.map(this.String.Get("default value")) and get a whole new tree is created that has the exact same shape as the original, but none of its values.
It assigns an object that has a property "Get" to this.String. "Get" is assigned an anonymous function, which will return a function that just returns the argument that was given to the first returning function. Sounds strange, but here is how it can be used:
var ten = this.String["Get"](10)();
ten will then contain a 10. Instead, you could have written the equivalent
var ten = this.String.Get(10)();
// saving the returned function can have more use:
var generatingFunction = this.String.Get("something");
alert(generatingFunction()); // displays "something"
That is, : just assigns some value to a property.
This answer may be a bit superflous since Tom's is a good answer but just to boil it down and be complete:-
this.String = {};
Adds an object to the current object with the property name of String.
var fn = function(val) {
return function() { return(val); }
}
Returns a function from a closure which in turn returns the parameter used in creating the closure. Hence:-
var fnInner = fn("Hello World!");
alert(fnInner()); // Displays Hello World!
In combination then:-
this.String = { Get: function(val) {
return function() { return(val); }
}
Adds an object to the current object with the property name of String that has a method called Get that returns a function from a closure which in turn returns the parameter used in creating the closure.
var fnInner = this.String.Get("Yasso!");
alert(fnInner()); //displays Yasso!

Categories

Resources