Extract the valid portions of a mostly JSON file

Viewed 109

I'm using jq to parse this file:

Successes: 0
Failures: 1
{
  name: '',
  success: false,
  error: '',
  data: {
    video: ''
  }
}

And obviously running jq < foo.json yields:

parse error: Invalid numeric literal at line 1, column 10

Because it can't parse

Successes: 0
Failures: 1

Is there way using Bash or jq itself to extract the remaining portion which is valid JSON?

{
  name: '',
  success: false,
  error: '',
  data: {
    video: ''
  }
}
5 Answers

If you add a line with '[' at the beginning and another with ']' at the end, then:

hjson -j  input.txt

yields:

[
  "Successes: 0",
  "Failures: 1",
  {
    "name": "",
    "success": false,
    "error": "",
    "data": {
      "video": ""
    }
  }
]

Since this is valid JSON, you can pipe it to jq to extract whichever pieces you want.

An interesting lossless possibility is to recognize key-value pairs:

jq 'map(if type == "string" and index(":") 
        then [splits(": *")]
             | {(.[0]): .[1] | (tonumber? // .)}
        else . end)'

With your sample, this would produce:

[
  {
    "Successes": 0
  },
  {
    "Failures": 1
  },
  {
    "name": "",
    "success": false,
    "error": "",
    "data": {
      "video": ""
    }
  }
]

Caveat

The above assumes that if a top-level item is a string with more than one colon, then it is acceptable to discard the string contents after the second colon.

To add another guess, while this is not valid JSON, at least some YAML parsers can handle it, which may be available to the next step in the chain you may be consuming input into and even easier than piping through jq

For example pyyaml for Python (common 3rd-party LibYAML-based parser) can handle this directly

>>> s = """{
...   name: '',
...   success: false,
...   error: '',
...   data: {
...     video: ''
...   }
... }"""
>>> yaml.safe_load(s)  # returns a Python dictionary
{'name': '', 'success': False, 'error': '', 'data': {'video': ''}}

I present this rather-gross solution for the 1-liner buffs

python3 -c "import sys,json,yaml; print(json.dumps(yaml.safe_load(''.join([x for x in sys.stdin][2:]))))"

imports libs
read each line from stdin
slice from the first two [2:]
reconnect 'em with .join
parse as yaml to a dictionary
stringify dict to json

Complete example (useless use of cat to demonstrate consuming from pipe)

cat << EOF | python3 -c "import sys,json,yaml; print(json.dumps(yaml.safe_load(''.join([x for x in sys.stdin][2:]))))" | jq
Successes: 0
Failures: 1
{
  name: '',
  success: false,
  error: '',
  data: {
    video: ''
  }
}
EOF
{
  "name": "",
  "success": false,
  "error": "",
  "data": {
    "video": ""
  }
}

If you just want to check, if the json is valid, you could do something like this:

jq < foo.json > /dev/null 2>&1
echo $?

If the json in foo.json is not valid, $? will contain something unequal 0. If $? == 0 -- then the json was valid.

BTW: It's not just because of the Success and Failures keys (which are outside of the json object) that your json is invalid -- also all the keys and string-values have to be quoted (with double quotes). So e.g. a valid version would be:

{
  "Successes": 0,
  "Failures": 1,
  "name": "",
  "success": false,
  "data": {
    "video": ""
  }
}

To emit all but the first two lines of a file, you can use the following:

( read; read; cat ) <foo.json

This will produce

{
  name: '',
  success: false,
  error: '',
  data: {
    video: ''
  }
}

But contrary to your claims, this is not valid JSON, and jq isn't able to work with that either. If that's your ultimate goal, use hjson to massage that JS into JSON as @peak demonstrated.

The following is a response to the task described in the title:

Extract the valid portions of a mostly JSON file

Given a file in which each JSON entity of interest has a type other than "string" and is on a single line, there is a jq solution that will extract them:

jq -nR 'inputs | fromjson? | select(type!="string")' input.txt

The following variant may also be of interest:

jq -nR 'inputs | fromjson?' input.txt
Related