How do I convert the output of ps(1) to JSON?

There are two obvious ways to represent columnar data output in JSON: as an array of arrays and as an array of objects. In the former case you convert each line of the input to an array; in the latter, to an object.

The commands listed bellow work at least with the output of procps-ng on Linux for the commands ps and ps -l.

Option #1: array of arrays

Using Perl

You can convert the output using Perl and the CPAN module JSON::XS.

# ps | perl -MJSON -lane 'my @a = @F; push @data, \@a; END { print encode_json \@data }'
[["PID","TTY","TIME","CMD"],["12921","pts/2","00:00:00","ps"],["12922","pts/2","00:00:00","perl"],["28280","pts/2","00:00:01","zsh"]]

Using jq

Alternatively, you can use jq itself to perform the conversion.

# ps | jq -sR '[sub("\n$";"") | splits("\n") | sub("^ +";"") | [splits(" +")]]' 
[
  [
    "PID",
    "TTY",
    "TIME",
    "CMD"
  ],
  [
    "16694",
    "pts/2",
    "00:00:00",
    "ps"
  ],
  [
    "16695",
    "pts/2",
    "00:00:00",
    "jq"
  ],
  [
    "28280",
    "pts/2",
    "00:00:02",
    "zsh"
  ]
]

Option #2: array of objects

You can convert the input to an array of JSON objects with meaningfully named keys by taking the key names from the header row.

This requires a little more effort and is slightly trickier in jq in particular. However, the result is arguably more human-readable.

Using Perl

# ps | perl -MJSON -lane 'if (!@keys) { @keys = @F } else { my %h = map {($keys[$_], $F[$_])} 0..$#keys; push @data, \%h } END { print encode_json \@data }'
[{"TTY":"pts/2","CMD":"ps","TIME":"00:00:00","PID":"11030"},{"CMD":"perl","TIME":"00:00:00","PID":"11031","TTY":"pts/2"},{"TTY":"pts/2","CMD":"zsh","TIME":"00:00:01","PID":"28280"}]

Note that the keys are in arbitrary order for each entry. This is an artifact of how Perl's hashes work.

Using jq

# ps | jq -sR '[sub("\n$";"") | splits("\n") | sub("^ +";"") | [splits(" +")]] | .[0] as $header | .[1:] | [.[] | [. as $x | range($header | length) | {"key": $header[.], "value": $x[.]}] | from_entries]'
[
  {
    "PID": "19978",
    "TTY": "pts/2",
    "TIME": "00:00:00",
    "CMD": "ps"
  },
  {
    "PID": "19979",
    "TTY": "pts/2",
    "TIME": "00:00:00",
    "CMD": "jq"
  },
  {
    "PID": "28280",
    "TTY": "pts/2",
    "TIME": "00:00:02",
    "CMD": "zsh"
  }
]

I would suggest as your starting point - don't use ps and then parse it. That's a good way of causing yourself pain (like say - you want to extend it to include command line args, which are space delimited).

So a simple one would be:

#!/usr/bin/env perl
use strict;
use warnings;
use JSON;
use Proc::ProcessTable;

my $json;
foreach my $proc ( @{ Proc::ProcessTable -> new -> table } ) { 
    push ( @$json, { %$proc } ); 
}

print to_json ( $json, { pretty => 1 } ); 

This will give you a full list of ps fields, which some may be redundant.

And if you want to make this a one liner:

perl -MJSON -MProc::ProcessTable -e 'print to_json ( [ map { %$_ } } @{ Proc::ProcessTable->new->table } ], { pretty => 1 } );'