How does one recursively generate a markup-like string from a JSON-compatible nested object/data-structure?

Viewed 86
let json =  {
    "user": {
        "first_name": "test",
        "second_name": "second name",
        "profile": {
            "test":{
                "img": "img"
            }      
        }
    },
    "company": {
        "name": "company 1",
        "company_nested": {
            "test" : "test"
        }
    }
}
let new_json = "";

function iterate(obj) {
    Object.keys(obj).forEach((key, index) => {
        let value = obj[key];
        if(typeof value === 'string') {         
            new_json += `"${key}": ${value}\n`;         
        }

        if (typeof value === 'object') {     
            new_json +=`##${key}##\n`;
            iterate(value);     
        }                   
    });
}
    
iterate(json);
$("#teste").html(new_json);

RESULT

##user##
"first_name": test
"second_name": second name
##profile##
##test##
"img": img

Is there a better way to change the Curly Brackets for something I want (a custom one)?

  • Open Bracket ({) must be ##KEY_NAME##
  • Clonse Bracket (}) must be ##/KEYNAME##

DESIRED RESULT

##user##
"first_name": test
"second_name": second name
##profile##
##test##
"img": img
##/test##
##/profile##
##/user##

##company##
"name": company 1
##company_nested##
"test": test
##/company_nested##
##/company##
3 Answers

You could loop thorugh the entries of the object. If the values is an object, recursively call the function and wrap the object within ##${k}## and ##/${k}##

const input={user:{first_name:"test",second_name:"second name",profile:{test:{img:"img"}}},company:{name:"company 1",company_nested:{test:"test"}}};

function replace(o) {
  return Object.entries(o).map(([k, v]) => {
    if (typeof v === 'object')
      return `##${k}##
${replace(v)}
##/${k}##`
    else
      return `"${k}": ${v}`
  }).join("\n")
}

console.log(replace(input))

If the multi-line template literal is hard to read, you could create an array and use join:

const replace = o =>
  Object.entries(o).map(([k, v]) =>
      typeof v === 'object' 
          ? [`##${k}##`, replace(v), `##/${k}##`].join('\n') 
          : `"${k}": ${v}`
  ).join("\n")

I will assume that your json is well formed. If not, you might have to handle in the code.

Step 1: Convert your json into a string.

let str = JSON.stringify(json);

Step 2: Create a Symbol Map that holds your existing symbol as a key and the new symbols as a value.

let symbolMap = new Map();
symbolMap.set("{", "$");
symbolMap.set("}", "%");

Iterate on each character in the string, check if the character exists in the map, if it exists, replace the new value.

for (let i = 0; i < str.length; i++) {
  if (symbolMap.get(str[i])) {
    str = str.replace(str[i], symbolMap.get(str[i]));
  }
}

Print the output

console.log(str);

Output: Before:

{"user":{"first_name":"test","second_name":"second name","profile":{"test":{"img":"img"}}},"company":{"name":"company 1","company_nested":{"test":"test"}}}

After:

$"user":$"first_name":"test","second_name":"second name","profile":$"test":$"img":"img"%%%,"company":$"name":"company 1","company_nested":$"test":"test"%%%

Full Code:

let json = {
  user: {
    first_name: "test",
    second_name: "second name",
    profile: {
      test: {
        img: "img",
      },
    },
  },
  company: {
    name: "company 1",
    company_nested: {
      test: "test",
    },
  },
};

let str = JSON.stringify(json);

let symbolMap = new Map();
symbolMap.set("{", "$");
symbolMap.set("}", "%");

console.log(str);

for (let i = 0; i < str.length; i++) {
  if (symbolMap.get(str[i])) {
    str = str.replace(str[i], symbolMap.get(str[i]));
  }
}

console.log(str);


This approach is based on a single recursively called reducer function. It also is a generic markup generating solution due to being configurable via the reducer function's first argument ...

function createCustomMarkupRecursively(collector, [key, value], idx) {
  collector.config = collector.config || {};
  collector.level = collector.level || 0;

  const {
    markup = '',
    config: {
      quote = '"',
      prefix = '##', suffix = '##',
      terminator = '/', separator = '\n',
    },
  } = collector;

  const markupBlockSeparator = ((collector.level === 0) && (idx > 0))
    ? separator
    : '';

  if (value && (typeof value === 'object')) {
    ++collector.level;

    collector.markup = [
      markup,
      markupBlockSeparator,
      `${ prefix }${ key }${ suffix }\n`,
      Object.entries(value).reduce(
        createCustomMarkupRecursively,
        Object.assign(collector, { markup: ''}),
      ).markup,
      `${ prefix }${ terminator }${ key }${ suffix }\n`,
    ].join('');

    --collector.level;
  } else {
    collector.markup = [
      markup,
      markupBlockSeparator,
      quote,
      key,
      quote,
      `: ${ value }\n`,
    ].join('');
  }

  return collector;
}

const data =  {
  user: {
    first_name: "test",
    second_name: "second name",
    profile: {
      test: {
        img: "img",
      },     
    },
  },
  company: {
    name: "company 1",
    company_nested: {
      test : "test",
    },
  },
};
const { markup } = Object
  .entries(data)
  .reduce(createCustomMarkupRecursively, {/*
    config: {
      quote: '"',
      prefix: '##',
      suffix: '##',
      terminator: '/',
      separator: '\n'
    },*/
    markup: '',
  });

// according to the OP ...
console.log(markup);

// customization sample ...
console.log(
  Object
  .entries(data)
  .reduce(createCustomMarkupRecursively, {

    config: {
      quote: "'",
      prefix: '<',
      suffix: '>',
      terminator: '$',
      separator: '\n<-- // -->\n\n',
    },
    markup: '## another markup configuration result ##\n\n',

  }).markup
)
.as-console-wrapper { min-height: 100%!important; top: 0; }

Related