Search code examples
javascriptlistobjecttaskgenerate

Transform nested list into object - Javascript


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.


Solution

  • 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));