Skip to content

Instantly share code, notes, and snippets.

@jmlon
Last active October 15, 2023 20:27
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jmlon/e0fcfdd79233ac4ec3c041cc31370bd2 to your computer and use it in GitHub Desktop.
Save jmlon/e0fcfdd79233ac4ec3c041cc31370bd2 to your computer and use it in GitHub Desktop.
/**
* Dereference object attributes from a string.
*
* Access nested object attributes using a string.
*
* @link https://gist.github.com/jmlon/e0fcfdd79233ac4ec3c041cc31370bd2
* @author Jorge M. Londoño
* @since 1.0.0
*/
'use strict';
const booleanRe = /^((?:true)|(?:false))(.*)/i;
const numericRe = /^(\d+(?:\.\d+(?:[eE][+-]\d+)?)?)(.*)/;
const stringRe = /^(?:["'](.*?)["'])(.*)/;
const name = /^([a-zA-Z_][\w\d_]*)(.*)/;
const attribute = /^(?:\.([\w_][\w\d_]*))(.*)/;
const arrayIndex = /^\[(.*)\](.*)/;
/**
* Returns the value of a field of data dereferenced by the string s
*
* @param {string} rest A string dereferencing some internal elemento of object data
* @param {object} data Object where to find values
* @return {any} The value dereferenced by the string
*/
export function objectParser(rest, result, context) {
[rest, result] = numericParser(rest, result);
if (rest && rest.length>0) {
[rest, result] = booleanParser(rest, result);
if (rest && rest.length>0) {
[rest,result] = stringParser(rest, result);
if (rest && rest.length>0) {
const match = rest.match(name);
if (match) {
const varname = match[1];
result = context[varname];
rest = match[2];
[rest,result] = attributeArrayParser(rest, result, context);
}
}
}
}
return [rest,result];
}
function attributeArrayParser(rest, result, context) {
if (rest && rest.length>0) {
if (rest.startsWith(']')) return [rest,result];
[rest,result] = attributeParser(rest, result, context);
[rest,result] = arrayDerefencingParser(rest, result, context);
[rest,result] = attributeArrayParser(rest, result, context);
}
return [rest,result];
}
function numericParser(rest, result) {
const match = rest.match(numericRe);
if (match) {
if (match[1].indexOf('.')>=0) {
result = parseFloat(match[1]);
rest = match[2];
}
else {
result = parseInt(match[1]);
rest = match[2];
}
}
return [rest, result];
}
function booleanParser(rest, result) {
const match = rest.match(booleanRe);
if (match) {
result = match[1]=='true';
rest = match[2];
}
return [rest, result];
}
function stringParser(rest, result) {
const match = rest.match(stringRe);
if (match) {
result = match[1];
rest = match[2];
}
return [rest,result];
}
function attributeParser(rest, result) {
if (rest.startsWith('.')) {
const match = rest.match(attribute);
if (match) {
result = result[match[1]];
rest = match[2];
// result = attributeArrayParser(match[2], result);
}
}
return [rest,result];
}
function arrayDerefencingParser(rest, result, context) {
if (rest && rest.startsWith('[')) {
let index;
[rest,index] = objectParser(rest.slice(1), result, context);
if (!rest.startsWith(']')) {
console.error('Bracket not closed');
return;
}
result = result[index];
rest = rest.slice(1);
if (rest && rest.length>0) {
[rest,result] = attributeArrayParser(rest, result, context);
}
}
return [rest,result];
}
/**
* Runs a test case using string s and local data object `testData`
*
* @param {string} s A string dereferencing some internal elemento of testData
* @param {any} expected The expected value
* @return {boolean} true if data returned is equal to expected value
*/
function testParser(s, expected) {
const testData = {
varname: 789,
object: { attr1: 456, attr2:"hello", attr3:false, attr4:[1,2,3], attr5:1, attr6:0 },
object2: { object3: { a:1, b:"wow", c: Math.PI }},
array: [1,true,"bye"],
index: 2
};
let passed = true;
const [rest,result] = objectParser(s, testData, testData);
if (result==undefined || result==null) {
console.log(s.padEnd(20), '\t', result, '\t', 'Failed');
passed = false;
}
else {
if (typeof(result)!='object' && typeof(expected)!='object') {
passed = result===expected;
console.log(s.padEnd(30), '\t', result.toString().padEnd(15), '\t', passed ? 'OK': 'X');
}
else
console.log(s.padEnd(30), '\t', result.toString().padEnd(15), '\t', '--comparison not implemented--');
}
return passed;
}
// Test cases
testParser("true", true);
testParser("false", false);
testParser('"string"', "string");
testParser("'string'", 'string');
testParser("123", 123);
testParser("1.23", 1.23);
testParser("1.23E+2", 123);
testParser("1.23E-2", 0.0123);
testParser("varname", 789);
testParser("object.attr1", 456);
testParser("object.attr4[0]", 1);
testParser("array[0]", 1);
testParser("array[1]", true);
testParser("array[2]", "bye");
testParser('object["attr1"]', 456);
testParser("object['attr2']", "hello");
testParser("object['attr3']", false);
testParser('object["attr4"]', [1,2,3]);
testParser("array[index]", "bye");
testParser("array[object.attr5]", true);
testParser("array[object['attr6']]", 1);
testParser('object2.object3.a', 1);
testParser('object2.object3["b"]', "wow");
testParser('object2["object3"]["c"]', Math.PI);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment