we need help to resolve this problem, no one in our enterprise was able to do it.
We have a String like this:
- name
- type
- string
- validation
- required
- minLength
- 4
- maxLength
- 20
- optionIn
- option1
- option2
- option3
- option4
- password
- type
- string
- validation
- required
- minLength
- 6
- maxLength
- 30
- date
- type
- date
And we need to generate an object like this:
{
name: {
type: 'string',
validation: {
required: true,
minLength: 4,
maxLength: 20,
optionIn: ['option1', 'option2', 'option3', 'option4']
}
},
password: {
type: 'string',
validation: {
required: true,
minLength: 6,
maxLength: 30
}
},
date: {
type: 'date'
}
}
A few things that make this really a complex task:
If the last nested item is just one, that makes him the value of the previous key. If the final nested item are more than one, they become an array, and the array is the value of the previous key.
Edit: Thanks @adiga for the insight, into the example 'required' becomes a object with value true, because his mates have a nested item
Is its a hard and complex task, libraries are available to use if you need to.
The solution I've come up with is a two step process.
First I parse()
the inputStr
into an intermediate form in the simplest way I can which ends up looking like this:
{
"name": {
"type": {
"string": null
},
"validation": {
"required": null,
"minLength": {
"4": null
},
"maxLength": {
"20": null
},
"optionIn": {
"option1": null,
"option2": null,
"option3": null,
"option4": null
}
}
},
"password": {
"type": {
"string": null
},
"validation": {
"required": null,
"minLength": {
"6": null
},
"maxLength": {
"30": null
}
}
},
"date": {
"type": {
"date": null
}
}
}
Then I transform()
that intermediate object into the final form.
const inputStr =
`- name
- type
- string
- validation
- required
- minLength
- 4
- maxLength
- 20
- optionIn
- option1
- option2
- option3
- option4
- password
- type
- string
- validation
- required
- minLength
- 6
- maxLength
- 30
- date
- type
- date`
let parseLimit = 1000;
function parse(lines, curIndent = 0) {
if (parseLimit-- < 0) throw "parseLimit exhausted";
if (lines.length === 0) return null;
const obj = {};
let parent = null;
let descendantLines = [];
[...lines, '>'.repeat(curIndent)].forEach(line => {
const indents = (line.match(/>/g) || []).length;
if (indents === curIndent) {
if (parent) {
obj[parent] = parse(descendantLines, curIndent + 1);
}
descendantLines = [];
parent = line.replace(/>/g, '');
} else if (indents > curIndent) {
descendantLines.push(line);
} else {
throw 'indents < curIndent';
}
});
return obj;
}
let transformLimit = 1000;
function transform(node) {
if (transformLimit-- < 0) throw "transformLimit exhausted";
const childKeys = Object.keys(node);
const leafChildKeys = childKeys.filter(childKey => {
return node[childKey] === null;
});
if (childKeys.length === leafChildKeys.length) {
//all leaf children
const values = childKeys.map(value => {
return isNaN(value)
? value
: +value;
});
return values.length === 1
? values[0]
: values;
} else { //not all leaf children
const newNode = {};
childKeys.forEach(childKey => {
if (leafChildKeys.includes(childKey)) {
//true
newNode[childKey] = true;
} else {
//recurs
newNode[childKey] = transform(node[childKey]);
}
});
return newNode;
}
}
function solve(str) {
const lines = str
.split('\n')
.map(line => line
.replace(/ /g, '>')
.replace('- ', '')
);
return transform(parse(lines));
}
console.log('input:\n', inputStr);
console.log('solution: ', solve(inputStr));