SeaWarrior404
SeaWarrior404

Reputation: 4157

How to extract the value of an object inside an incomplete JSON array in Javascript?

I have a node app from which I get some data. I have gone through other questions asked on SO..but Im unable to figure this out. I need to access the value of result . The code below shows the exact response I get from server. None of the methods described in other answers like JSON.parse() etc seem to work.

    [{
      query: {
                "parameter1": "12",
                "parameter2": "13",
                "parameter3": 25
               }
      result: 6.58443
    }]

EDIT : As mentioned in the comments below, unfortunately I cant fix this on the server side(Comes from an external source). I have to deal with this broken JSON on my end and extract the value of result. EDIT 2 : Yes, there are multiple arrays like this. The content and comma part doesnt change. They are listed one after the other.

Upvotes: 0

Views: 819

Answers (4)

Joel Cornett
Joel Cornett

Reputation: 24788

Purely for illustrative purposes, the below code "rewrites" JSON that is missing commas into JSON that has commas in the appropriate places. The advantage of using this over a replace or a regular expression is that this code guarantees that string literals are handled correctly:

const LEX_EXPR = (
  '('
    + '"(?:\\\\(?:["\\\\/bfnrt]|u[a-fA-F0-9]{4})|[^"])*"|'
    + '-?\\d+(?:\\.\\d+)?(?:[eE][+-]?\\d+)?|'
    + '(?:true|false|null)'
  + ')|'
  + '([{\\[])|'
  + '([}\\]])|'
  + '([:,])|'
  + '(\\s+)|'
  + '(.)'
)

function lex(string) {
  let tokens = []
  let expr = new RegExp(LEX_EXPR, 'mguy')
  let match = expr.exec(string)
  while(match !== null) {
    let [
      value,
      atom,
      begin, end, sep,
      whitespace,
      junk
    ] = match
    let type
    if (atom != null) {
      type = "atom"
    } else if (begin != null) {
      type = "begin"
    } else if (end != null) {
      type = "end"
    } else if (sep != null) {
      type = "sep"
    } else if (whitespace != null) {
      type = "whitespace"
    } else {
      // junk. ignore or raise exception
      throw `Invalid character: ${junk}`
    }
    tokens.push({ type, value })
    match = expr.exec(string)
  }
  return tokens
}

function shouldInsertComma(prev, cur) {
  if (!prev || !cur) {
    return false
  }
  if (prev.type == "begin" || prev.type == "sep") {
    return false
  }
  return cur.type == "begin" || cur.type == "atom"
}

function rewrite(tokens) {
  let out = []
  let prevNonWhitespace = null
  for (let i = 0; i < tokens.length; i++) {
    let cur = tokens[i]
    if (cur.type !== "whitespace") {
      if (shouldInsertComma(prevNonWhitespace, cur)) {
        out.push({ type: "sep", value: "," })
      }
      prevNonWhitespace = cur
    }
    out.push(cur)
  }
  return out
}

function joinTokens(tokens) {
  return tokens.map(({ value }) => value).join('')
}

const invalid = `
{
  "foo": {
    "bat": "bing}"
    "boo": "bug"
  }
  "result": "yes"
}
`
const rewritten = joinTokens(rewrite(lex(invalid)))
console.log(JSON.parse(rewritten))  // { foo: { bat: 'bing}', boo: 'bug' }, result: 'yes' }

Upvotes: 0

SeaWarrior404
SeaWarrior404

Reputation: 4157

The suggestions provided above..all point to a hacky way of solving this. The only way to solve this issue was with the help of good old Regex expressions. To my surprise..even though there are lots of libraries to handle JSON parsing etc, to solve edge cases(Common when dealing with small clients or unreliable data source), there is no library which can handle this scenario. @Bitifet's answer is what solves this problem..with regex.

Upvotes: 0

bitifet
bitifet

Reputation: 3659

Despite the fact you can't receive that data though any library function that expects JSON like jQuery $.ajax() with dataType='json' option (you should use dataType="text" in this case to avoid premature error being triggered I mean).

...you obviously need to fix JSON syntax before parsing it (as I understood you already know).

If it is what you are asking for, your best bet is a regular expression search and replace.

If you know you won't get things such as '{bracket: "}"}' it is pretty simple:

Example:

var wrong = `
    [{
      "query": {
                "parameter1": "12",
                "parameter2": "13",
                "parameter3": 25
               }
      "result": 6.58443
    }]
`;

var good = wrong.replace(/}(?!\s*[,}\]])/g, '},');

var json = JSON.parse(good);

console.log(json);

This is the simplest example that fixes the input you provided.

Even though it does not fix the same problem after an end of array (']') and, most importantly, if it were fixed it (or simply the string ended with '}' instead of ']' it would added an extra ',' at the end messing up things again.

A more polite approach solving beferementioned issues is to replace the var good = ... row in previous code with this one:

var good = wrong.replace(/(}|])(?!\s*[,}\]])/g, '$1,')
    .replace(/,\s*$/, '')
;

Now, you have a valid json object so accessing any property in it is pretty obvious. For example, json[0].result is what you asked for.

On the other hand, if you can have brackets inside literal strings, it will be much more difficult (even not impossible). But I figure out it would hardly be the case...

Upvotes: 2

scraaappy
scraaappy

Reputation: 2886

what you can do is to encapsulate your result with back-ticks to have a (valid) string literal, then get result with any method you want, for example a matching regex :

var arr =  `[{
      "query": {
                "parameter1": "12",
                "parameter2": "13",
                "parameter3": 25
               }
      "result": 6.58443
}]`;

var match = arr.match(/("result": )(\d*.\d*)/);

console.log(match[2]);

Upvotes: 0

Related Questions