0

In javascript code, I have a string that defines a property path within an object. For instance

var def = "contact.email"

How can I get the following function from this string?

o => o.contact.email

So that I can use it in the following way:

var person = {
    name: 'Test',
    contact: { email: '[email protected]' }
}

var emailGetter = MagicCompileFunction('contact.email');

var email = emailGetter(person);
// here, email should hold the value of person.contact.email

The path string is unknown at compile time. It could be provided by the user as well.

The solution should work in non-browser environments too (where there is no window object), for instance in NodeJS server side javascript.

I know that one solution would be to create a generic method that takes an object and a string as arguments (valueGetter(person, "contact.email") for instance), where the string defines the path within the object, and then split the string on each dot '.' and follow the path on the object. But I don't want this algorithm to execute in every call to getter function. I need a dynamically compiled method that would give me a final getter that would access the desired (sub)property immediately.

9
  • 1
    Why do you need a final getter exactly? The solution, if you need it exactly as specified would be using eval, which is a terrible idea in most cases, including this one. A generic method would be a much better solution. Commented Apr 12, 2017 at 13:11
  • 1
    I would try a "generic" method and see how it performs before trying to cook up some science project. Commented Apr 12, 2017 at 13:11
  • In fact I don't even think what you're asking makes sense, unless you want to "compile" directly into the runtime intermediate code, which would definitely be a "science project". Once you know the property names, it's still necessary to sequentially fetch the property values from each object in the chain. Commented Apr 12, 2017 at 13:14
  • @Pointy this is a method that will be probably be executed hundreds or thousands of times. I think a generic method that contains a string split and a for loop, to loop over each property in the path will be less efficient that dictionary look-ups. On the other hand, using a generic method would make it easier to avoid accessing properties of undefined objects, but that would add an extra if check within the loop. Commented Apr 12, 2017 at 13:26
  • If that's really the case, then perhaps you should re-think the way you're data structure is built. I mean, if there are significant performance considerations then it might be good to explore other aspects of the system that could be changed. Commented Apr 12, 2017 at 13:35

4 Answers 4

3

Slightly more ES6 than Archer's answer:

function MagicCompileFunction(mapping) {
	return (obj => mapping.split(".").reduce((curr, name) => curr[name], obj));
}

var person = {
    name: "Test",
    contact: {
		email: "[email protected]"
	}
}

var emailGetter = MagicCompileFunction("contact.email");
var email = emailGetter(person);

var nameGetter = MagicCompileFunction("name");
var name = nameGetter(person);

console.log(email);
console.log(name);

Sign up to request clarification or add additional context in comments.

Comments

2

You can achieve this using Function constructor

var person = {
    name: 'Test',
    contact: { email: '[email protected]' }
}
var maginFunction = (def) => new Function('o','return o.'+ def);

var emailGetter = maginFunction('contact.email');

var email = emailGetter(person);
console.log(email);

8 Comments

This is marked as the best solution because it seems it does exactly what I want. All the other answers don't avoid loops, string splitting or array manipulation.
However please be really careful with what you allow 'def' to be here. As you said, it may come from the users and run on the server. You'll want to be extremely careful they won't give you "constructor && require('child_process').exec( 'rm -rf /' )".
@Redu well "hacking" may not be an issue if it's only used with data under the site's control. What I'm trying to figure out is if a function created with the Function constructor is a candidate for optimization. (The unoptimized version of this might be faster than an iterative solution anyway, of course.)
Well then yes, that could be risky. However, in other news that function (emailGetter) does get optimized, in V8 at least. (See this article.).
It is a dangerous approach indeed. The input string may originate on the user's side but it will match a certain regex before going into the Function constructor.
|
1

I like the question so I came up with a solution. This should do what you ask, without any need for using eval...

function MagicCompileFunction(mapping) {
	mapping = mapping.split(".");
	return (function(obj) {
		var result = obj;
		for (var idx in mapping) {
			result = result[mapping[idx]];
		}
		return result;
	});
}

var person = {
    name: "Test",
    contact: {
		email: "[email protected]"
	}
}

var emailGetter = MagicCompileFunction("contact.email");
var email = emailGetter(person);

var nameGetter = MagicCompileFunction("name");
var name = nameGetter(person);

console.log(email);
console.log(name);

The MagicCompileFunction() returns a function that is primed with the mapping that you use when you create the getter object. Then you can pass any person into that object to return the related data.

1 Comment

Your for loop probably should be a plain for loop and not a for ... in, but yes this is probably the best idea.
0

You may dynamically access the nested object properties simply as follows;

function dynPropAcc(o,s){
  return s.split(".")
          .reduce((p,q,i) => i-1 ? p[q] : o[p][q]);
}

var def = "contact.email.2",
    obj = {contact: {name: "John", email: { 1: "[email protected]", 2: "[email protected]"}}};

console.log(dynPropAcc(obj,def));

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.