Convert a string into an array of objects

Viewed 129

Currently I have this string:

"Hello, I'm <%first%> <%last%>, and I <3 being a <%occupation%>. I am > blah";

How do I separate it into a an array of object that looks like this:

[
  { type: 'string', value: 'Hello, I'm ' },
  { type: 'token', value: 'first' },
  { type: 'string', value: ' ' },
  { type: 'token', value: 'last' },
  { type: 'string', value: ', and I <3 being a ' },
  { type: 'token', value: 'occupation' },
  {type: 'string', value: 'I am > blah'}
]

The pattern of the string is that if we find a word that looks like this: <%word%>, then we put that into the array as an object with type token. Otherwise we put it in as a type string. My question is how do we implement it in code.

I'm having difficulty with forming the phrase which is going to be the value for the key value. Below is a code that I tried to implement, but it is faulty. My idea is to have a word variable, which is empty, and as it goes through the string it concats the words to form the phases. Once it see that it's in between <% and %>, it will be given a type token and push into the arrStr array.

However 2 issues: 1) It seems that with this code, each iteration of the string from "H" to "He" to "Hel" to "Hell" to "Hello" is being generated. 2) It seems like it never touches token. 3) The first "h" in "hello" is somehow omitted.

How do I do this without using regex?

7 Answers
var str = "Hello, I'm <%first%> <%last%>, and I <3 being a <%occupation%>. I am > blah";
var arr = str.split('<%');
var final = [], vala, thetype;
arr.forEach(val => {
 vala=val.split('%>');
 thetype='string';
 if (vala.length>0) thetype='token';
 final.push({type: thetype, value: vala[0]});
})

I'm sure regex will be a better solution, but I don't know it. So here is my approach:

let txt = "Hello, I'm <%first%> <%last%>, and I <3 being a <%occupation%>. I am > blah";

var arr = [];

txt.split("<%").forEach(x => {
  if (x.includes("%>")) {
    x.split("%>").forEach((y, i) => {
      arr.push({type: i == 0 ? 'token' : 'string', value: y});
    })
  } else {
    arr.push({type: 'string', value: x});
  }
});

console.log(arr);

Using Replace Twice


Let's replace the angular brackets using some dummy string to split the data easily.

So, we replace <% by *# and %> only by *.

Then we split the data by only *. And after splitting we have the following array

[
  "Hello, I'm ",
  "#first",
  " ",
  "#last",
  ", and I <3 being a ",
  "#occupation",
  ". I am > blah",
]

So, the extra # that we added to only the opening angular bracket will help us differentiate a token from a string.

Now we can simply map this array and get the desired result.

NOTE: You can use an appropriate dummy string that you're sure won't appear in the string.

const 
  str = "Hello, I'm <%first%> <%last%>, and I <3 being a <%occupation%>. I am > blah",
      
  res = str
    .replace(/<%/g, "*#")
    .replace(/%>/g, "*")
    .split("*")
    .map((v) =>
      v[0] === "#"
        ? { type: "token", value: v.slice(1) }
        : { type: "string", value: v }
    );

console.log(res);

You could parse the string with a regexp like this:

const input = "Hello, I'm <%first%> <%last%>, and I <3 being a <%occupation%>. I am > blah";

const regex = /<%([^%]+)%>/g;

let result;
let parsed = [];
let lastIndex = 0;
while(result = regex.exec(input)) {
  const { 1: match, index } = result;
  parsed.push({ type: 'string', value: input.substring(lastIndex, index) });
  parsed.push({ type: 'token', value: match });
  lastIndex = index + match.length + 4;
}
parsed.push({ type: 'string', value: input.substring(lastIndex) });


console.log(parsed);

Using regex:

const convertToObjectArr = (str = '') => {
  const reg = /<%(.*?)%>/g;
  let arr = [];
  let index = 0, lastIndex = 0;

  while (match = reg.exec(str)) {
    index = match.index;
    if(index > lastIndex) {
      arr.push({ type: 'string', value: str.substring(lastIndex, index) });
    }
    lastIndex = reg.lastIndex;
    arr.push({ type: 'token', value: str.substring(lastIndex-2, index+2) });
  }
  
  if (lastIndex == 0) {
    arr.push({ type: 'string', value: str.substring(lastIndex) });
  } else if (lastIndex < str.length) {
    arr.push({ type: 'token', value: str.substring(lastIndex) });
  }
  
  return arr;
}

console.log( convertToObjectArr("Hello, I'm <%first%> <%last%>, and I <3 being a <%occupation%>. I am > blah") );
console.log( convertToObjectArr("Hello") );
console.log( convertToObjectArr("Hello, I'm <%first%>") );

Here was my approach. You could simplify this a lot if you used regex (this is basically the exact use case for regex)

target = "Hello, I'm <%first%> <%last%>, and I <3 being a <%occupation%>. I am > blah"

const split = (el) => {
    const parts = el.split('%>')
    return [{type: 'token', value: parts[0]}, {type: 'string', value: parts[1]}]
}

const parts = target.split('<%')
              .map(el => 
                    el.indexOf('%>') === -1 
                    ? {type: "string", value: el} 
                    : split(el)
              ).flat()

console.log(parts)

You can use a lexer to tokenize your string. I have used moo in this example:

const lexer =
  moo.compile({ token: {match: /<%.+?%>/, value: raw => raw.slice(2, -2)}
              , strlt: /.+?(?=<%)/
              , strrt: /(?<=%>).+/ });

lexer.reset("Hello, I'm <%first%> <%last%>, and I <3 being a <%occupation%>. I am > blah");

console.log(

  [...lexer].map(({ type, value }) =>
    ({ type: (type === 'token' ? type : 'string')
     , value }))

);
<script src="https://unpkg.com/moo@0.5.1/moo.js"></script>

Related