javascript insert method arguments into string template - javascript

I'm not sure exactly how the final implementation will be, but the basics are to insert method arguments into a string "template". The first instance, I could just do a regex replace but that has some downfalls, which I'm willing to accept if necessary. The second one is a bit more difficult. How can I get the names from the template and replace with matched from the passed object? Thanks for any help.
var myTemplate = 'Hello {name}'; // or something similar
var name = 'Bob';
function applyTemplate(tpl,str) {
//do stuff here to replace {name} with passed argument
}
var newStr = applyTemplate(myTemplate,name); //should return 'Hello Bob'
//Also this one
var myTemplate = 'Good {timeOfDay} {name}';
function applyTemplate(tpl,o) {
//simple objects only, don't need nested
}
var newStr = applyTemplate(myTemplate,{name:'Bob',timeOfDay:'morning'}); //should return 'Good morning Bob'

If you don't need extra checking&validation, you could just replace the {key} with the value such as :
function applyTemplate(tpl,o) {
for(var key in o)
{
if(o.hasOwnProperty(key))// prevent iteration on any prototype inherited methods
tpl = tpl.replace('{'+key+'}',o[key]);
}
return tpl;
}
As for your simple first applyTemplate function, since you do not have any notion about what the key should be, you can use regex to replaceonly the first {...} encountered :
function applyTemplate(tpl,str) {
return tpl.replace(/{.*?}/,str);
}
And then, of course , you can combine these two functions in one, with slightly different functionalities based on the type of the arguments:
function applyTemplate(tpl,o) {
switch(typeof o){
case 'object' :
for(var key in o)
{
if(o.hasOwnProperty(key))
tpl = tpl.replace('{'+key+'}',o[key]);
}
break;
case 'string' :
tpl = tpl.replace(/{.*?}/,o);
break;
default :
throw new Error('no valid parameters supplied');
}
return tpl;
}
This should do the trick. If you are interested, you could take a peak at the jquery template system : http://api.jquery.com/jQuery.template/.
Hope this helped.

Related

javascript Object - access child property using dot notation

I created an object that requires a dictionary object be passed to initialize, and has attributes (like length). I'd like to be able to simply access the dictionary's key : value pair by passing something like:
myNewObj['a'] or myNewObj.a
and not having to instead pass myNewObj.dictionary['a'], but can't figure out how to do so.
Is there a way to set a dynamic attribute that, if no other attribute or method exits, will instead look into the dictionary and find the associated key?
var newObject = function(dictionary) {
this.dictionary = dictionary;
this.length = dictionary[[Object.keys(dictionary)[0]]].length;
this.get = function (column) {
return this.dictionary[[column]];
}
};
var myNewObj = new newObject({
a : [1,2,3],
b : [4,5,6]
});
console.log(myNewObj.get('a'));
I've updated this to show a .get() method that works, but it's still not exactly what I'm looking for.
jsFiddle: http://jsfiddle.net/2uMjv/571/
Although this might not exactly fit your use case of dynamic attributes, the closest thing I can think of is extending the prototype. At least by using Object.setPrototypeOf* you can avoid defining each method individually and have a base set of functions to apply to each one of these newObject's.
var newObject = function(dictionary) {
this.dictionary = dictionary;
this.length = dictionary[[Object.keys(dictionary)[0]]].length;
this.get = function(column) {
return this.dictionary[[column]];
}
};
// Essentially a wrapper constructor.
var createNewObject = function(dictionary) {
Object.setPrototypeOf(dictionary, new newObject(dictionary));
return dictionary;
}
var myNewObj = createNewObject({
a : [1,2,3],
b : [4,5,6]
});
console.log(myNewObj['a']);
console.log(myNewObj.get('b'));
*You may want to look at the potential drawbacks in using this.
Why don't you just set properties? :
class Dictionary {
constructor(dictionary) {
Object.assign(this, dictionary);
}
get length() {
return Object.keys(this).length;
}
get(column) {
return this[column];
}
}
var dict = new Dictionary({
a : [1,2,3],
b : [4,5,6]
});
console.log(dict.a, dict["a"], dict.get("a"));

Delete item from object by selector string

I am trying to delete an item from an object by passing a key to the method. For example I want to delete a1, and to do so I pass a.a1 to the method. It then should delete a1 from the object leaving the rest of the object alone.
This is the structure of the object:
this.record = {
id: '',
expiration: 0,
data: {
a: {
a1: 'Cat'
}
}
}
I then call this method:
delete(key) {
let path = key.split('.')
let data = path.reduce((obj, key) => typeof obj == 'object' ? obj[key] : null, this.record.data)
if(data) delete data
}
Like this:
let inst = new MyClass()
inst.delete('a.a1')
This however gives me the following error:
delete data;
^^^^
SyntaxError: Delete of an unqualified identifier in strict mode.
I assume that data is a reference still at this point, or is it not?
Maybe reduce isn't the right method to use here. How can I delete the item from the object?
Using your example, the value of data at the point where it is checked for truthiness is Cat, the value of the property you're trying to delete. At this point, data is just a regular variable that's referencing a string and it's no longer in the context of inst.
Here's a solution I managed to get to work using the one from your OP as the basis:
let path = key.split('.')
let owningObject = path.slice(0, path.length - 1)
.reduce((obj, key) => typeof obj == 'object' ? obj[key] : null, this.record.data)
if (owningObject) delete owningObject[path[path.length - 1]]
The main difference between this and what you had is that reduce operates on a slice of the path segments, which does not include the final identifier: This ends up with owningObject being a reference to the a object. The reduce is really just navigating along the path up until the penultimate segment, which itself is used as the property name that gets deleted.
For an invalid path, it bails out either because of the if (owningObject) or because using delete on an unknown property is a no-op anyway.
The solution I came up with which I am not super fond of but works, is looping over the items which will allow me to do long keys like this
a.a1
a.a1.a1-1
a.a1.a1-1.sub
The function then looks like this
let record = {
data: {
a: {
a1: 'Cat',
a2: {
val: 'Dog'
}
}
}
}
function remove(key) {
let path = key.split('.')
let obj = record.data
for (let i = 0; i < path.length; i++) {
if (i + 1 == path.length && obj && obj[path[i]]) delete obj[path[i]]
else if(obj && obj[path[i]]) obj = obj[path[i]]
else obj = null
}
}
// Removes `a.a1`
remove('a.a1')
console.log(JSON.stringify(record))
// Removes `a.a2.val`
remove('a.a2.val')
console.log(JSON.stringify(record))
// Removes nothing since the path is invalid
remove('a.a2.val.asdf.fsdf')
console.log(JSON.stringify(record))
You can delete keys using [] references.
var foo = {
a: 1,
b: 2
};
var selector = "a";
delete foo[selector];
console.log(foo);
I'm not sure if this helps you but it might help someone googling to this question.
Here's another method which is very similar to the OP's own solution but uses Array.prototype.forEach to iterate over the path parts. I came to this result independently in my attempt to wrap this up as elegantly as possible.
function TestRecord(id, data) {
let record = {
id : id,
data : data
};
function removeDataProperty(key) {
let parent = record.data;
let parts = key.split('.');
let l = parts.length - 1;
parts.forEach((p, i) => {
if (i < l && parent[p]) parent = parent[p];
else if (i == l && parent[p]) delete parent[p];
else throw new Error('invalid key');
});
}
return {
record : record,
remove : function(key) {
try {
removeDataProperty(key);
} catch (e) {
console.warn(`key ${key} not found`);
}
}
}
}
let test = new TestRecord('TESTA', {
a : { a1 : '1', a2 : '2' },
b : { c : { d : '3' } }
});
test.remove('a'); // root level properties are supported
test.remove('b.c.d'); // deep nested properties are supported
test.remove('a.b.x'); // early exit loop and warn that property not found
console.log(test.record.data);
The usage of throw in this example is for the purpose of breaking out of the loop early if any part of the path is invalid since forEach does not support the break statement.
By the way, there is evidence that forEach is slower than a simple for loop but if the dataset is small enough or the readability vs efficiency tradeoff is acceptable for your use case then this may be a good alternative.
https://hackernoon.com/javascript-performance-test-for-vs-for-each-vs-map-reduce-filter-find-32c1113f19d7
This may not be the most elegant solution but you could achieve the desired result very quickly and easily by using eval().
function TestRecord(id) {
let record = {
id : id,
data : {
a : {
a1 : 'z',
a2 : 'y'
}
}
};
return {
record : record,
remove : function (key) {
if (!key.match(/^(?!.*\.$)(?:[a-z][a-z\d]*\.?)+$/i)) {
console.warn('invalid path');
return;
} else {
let cmd = 'delete this.record.data.' + key;
eval(cmd);
}
}
};
}
let t = new TestRecord('TESTA');
t.remove('a.a1');
console.log(t.record.data);
I have included a regular expression from another answer that validates the user input against the namespace format to prevent abuse/misuse.
By the way, I also used the method name remove instead of delete since delete is a reserved keyword in javascript.
Also, before the anti-eval downvotes start pouring in. From: https://humanwhocodes.com/blog/2013/06/25/eval-isnt-evil-just-misunderstood/ :
...you shouldn’t be afraid to use it when you have a case where eval()
makes sense. Try not using it first, but don’t let anyone scare you
into thinking your code is more fragile or less secure when eval() is
used appropriately.
I'm not promoting eval as the best way to manipulate objects (obviously a well defined object with a good interface would be the proper solution) but for the specific use-case of deleting a nested key from an object by passing a namespaced string as input, I don't think any amount of looping or parsing would be more efficient or succinct.

How does JSON.stringify make memoize function work?

_ .memoize = function(func) {
var hash = {};
return function() {
var arg = JSON.stringify(arguments);
if (hash[arg] === undefined) {
hash[arg] = func.apply(this, arguments);
}
return hash[arg];
};
};
Hello,
I am trying to implement the memoize underscore function. I have a question regarding to JSON.stringify.
In the if statement where it checks if the arg already exist or not in the hash. Why using JSON.stringify make it possible to check wether the input arg exist or not in the hash. I mean if we pass the arguments array without converting them using JSON.stringify, then we cannot check because we are passing an entire array. However, when using JSON.stringify, it makes it work. So how does JSON.stringify make it possible to check ?
The hash is a JavaScript object, which uses strings as keys. You cannot use an array (or array-like, in the case of arguments) there, so it needs to be converted to a string.
If no custom conversion is done, then the default serialisation would be "[object Arguments]" for any value of arguments. This is not unique and will not work with the intention of memoization.
var hash = {};
var i = 0;
//a naive function that takes anything and puts it in a hash with a unique value
function populateUnique() {
hash[arguments] = "Hello" + i;
i++;
}
populateUnique("a");
populateUnique("b");
populateUnique("c", "d", "e");
console.log(hash); //only shows the last thing, as it it's always overridden.
This implementation chooses to employ JSON.stringify because it is quite straight forward - you could implement a custom serialisation function, but there is already one provided, so this is the simplest way to do it.
Should be noted that JSON.stringify is not bulletproof. It is easy to use and covers a lot of cases, but may blow up, for example, if you have circular references:
var foo = {};
foo.bar = foo;
JSON.stringify(foo);
Since the memoize function does not control what will be passed in as arguments, it's possible that one of them, that is normally perfectly valid, will throw an error.
Another problem is if any of the arguments has its own toJSON method - this will be used for serialization, so you could end up in an interesting situation:
var a = 42;
var b = {
firstname: "Fred",
lastname: "Bloggs",
id: 42,
toJSON: function() { return this.id }
}
console.log(JSON.stringify(b));
console.log(a == JSON.stringify(b));
It's because only strings can be used as keys in javascript objects.
For example:
var key = {a:1};
var map = {};
map[key] = 1;
// {'[object Object]': 1}
This will result in every arguments combination being saved in the same key.
Using JSON.stringify transform the arguments list in an unique string that can in turn be used as an unique object key.
var key = {a:1};
var map = {};
map[JSON.stringify(key)] = 1;
// {'{"a":1}': 1}
This way, every time you call the function with the same arguments, JSON.stringify will return the same unique string and you can use that to check if you already have a cached result for that set of arguments, and if so, returning the cached value.

How to use String representation of object property, operator, and value?

I'm trying to use a string value of say, "[ScheduledDate] < '11/1/2011'", and test for a bool value on an object like "item". But I can't seem to be able to figure out a way to do it successfully. I'm not wanting to use the eval function, but if it's the only way, then I guess I will have to. below is an example of the function I'm trying to use.
function _filterItem2() {
var item = { ScheduledDate: '1/1/2012' };
var filterExpression = "[ScheduledDate] < '11/1/2011'";
var result = item[filterExpression]; // This is where I'm not sure.
return result;
}
No, item[filterExpression] would just return the property named like your string.
Instead, you should store your filter expression as an object:
var filter = {
propname: "ScheduledDate",
operator: "<",
value: "1/1/2012"
};
Then get your comparison values:
var val1 = item[filter.propname],
val2 = filter.value;
and now comes the tricky part. You are right, you should not use eval. But there is no possibility to get the corresponding functions from operator names, so you will need to code them yourself. You might use a switch statement or a map like this:
var operators = {
"<": function(a,b){return a<b;},
">": function(a,b){return a>b;},
...
};
var bool = operators[filter.operator](val1, val2);

Mapping JSON Objects to Javascript Objects

When using AJAX, I tend to pass objects from my server to Javascript in the form of JSON objects (aka Javascript). Certain functions within my Javascript rely on the specific type of object I am using. For instance, lets use a phone number for example. I have a constructor:
function PhoneNumber(number, type, isPrimary, contactId, id, className) {
this.number = number;
this.type = type;
this.isPrimary = isPrimary;
this.contactId = contactId;
this.id = id;
this.className = className;
}
Which I use when creating a phone number object in my Javascript. In some situations I don't create the object in JS, I get the object from the server so it comes in the form of a generic object with the exact same fields. So when my code relies on the specific type by using something such as this:
var objType = objArray[i].constructor.name;
var mappedObj;
switch(objType) {
case 'PhoneNumber':
currentArray = currentArray.phone;
//Convert response to javascript object.
mappedObj = mapPhone(jsonResponse[i]);
break;
case 'Email':
currentArray = currentArray.email;
mappedObj = mapEmail(jsonResponse[i]);
break;
case 'Address':
currentArray = currentArray.address;
mappedObj = mapAddress(jsonResponse[i]);
break;
case 'Website':
currentArray = currentArray.website;
mappedObj = mapWebsite(jsonResponse[i]);
}
In this situation, I check the name of the objects constructor and set certain variables based on that name. If the object I check the name on is a JSON from the server, it simply gives me a generic "Object" response and thus the code does not work. I get around this by using a mapping function for each object such as:
function mapPhone(phoneObj) {
var id = phoneObj.id;
var contactId = phoneObj.contactId;
var number = phoneObj.number;
var type = phoneObj.type;
var primary = phoneObj.isPrimary;
var className = phoneObj.className;
var phoneNumber = new PhoneNumber(number, type, primary, contactId, id, className);
return phoneNumber;
}
This works just fine, but to me seems a little redundant. Is this the best way to solve the JSON Object problem, or is there a better solution? I understand this is more of a "Am I doing this the best way possible" type of question, but I repeat this type of logic CONSTANTLY in my Javascript code and I figure I might as well get another opinion or two on whether or not its the proper way to do this before I have to spend hour upon hour fixing it in the future.
EDIT: I ended up accepting a jQuery solution because I happen to use jQuery in my project. There are however multiple solutions that also worked for me before I found the jQuery option. They just weren't quite as clean and efficient.
The following requires you to have the same properties in your object and your JSON object.
var phoneNumber = $.extend(new PhoneNumber(), yourJSONObject);
This basically creates a new PhoneNumber object and then copies all properties from your JSON object onto it. The $.extend() method is from jQuery, but you could also use as similar method from e.g. Underscore.js or one of the other js libraries/frameworks.
This similar question has a lot of interesting answers:
Parse JSON String into a Particular Object Prototype in JavaScript
Based off the poster's own answer, I think this would be an effective solution for you:
function recastJSON(jsonObject) {
// return generic object if objectType is not specified
if (!jsonObject.objectType)
return jsonObject;
// otherwise create a new object of type specified
var obj = eval('new '+jsonObject.objectType+'()');
for(var i in jsonObject)
obj[i] = jsonObject[i];
return obj;
}
You will need to add objectType to the JSON objects you are receiving to define the javascript class you want to instantiate. Then when you call this function, it will cast the object to that type and copy the data over (including the variable 'objectType').
Using your phone number example, your code would look like this:
// generic object from JSON
var genericObject = {objectType:'PhoneNumber', number:'555-555-5555', type:'home', primary:true, contactId:123, id:1234, className:'stdPhone'};
var phoneObject = recastJSON(genericObject);
AFAIK, in everything that is not IE, you can do this:
// define a class
var Foo = function(name) {
this.name = name;
}
// make a method
Foo.prototype.shout = function() {
return "I am " + this.name;
}
// make a simple object from JSON:
var x = JSON.parse('{ "name": "Jason" }');
// force its class to be Foo
x.__proto__ = Foo.prototype;
// the method works
x.shout();
Unfortunately, IE does not support the __proto__ accessor, so what you would need to do is first create an empty instance of your object, then just copy everything over:
// make a simple object from JSON:
var x = JSON.parse('{ "name": "Jason" }');
// make an empty Foo
var y = Object.create(Foo.prototype);
// copy stuff over
for (a in x) {
y[a] = x[a];
}
y.shout();
Both of these approaches are quite a bit more generic than your mapWhatever functions, keeping it DRY.
If not supporting older browsers is ok, You can use Object.create to do the mapping for you. (dropping the shim—at least the shim at MDN—in will not fix older browsers, since that shim does not accept the second parameter.)
DEMO
function makeThisExtend(obj, CtorFunc) {
for (var k in obj)
if ({}.hasOwnProperty.call(obj, k))
obj[k] = { value: obj[k] };
return Object.create(CtorFunc.prototype, obj);
}
var objFromServer = { Number: "123", id: 5 };
objFromServer = makeThisExtend(objFromServer, PhoneNumber);
alert(objFromServer.Number + " " + objFromServer.id); //123 5
alert(objFromServer.constructor); //function PhoneNumber ...

Categories