DEV Community

Liz Lam
Liz Lam

Posted on • Edited on

Use jq to format p4 changes output to JSON.

UPDATE: Just a few days after hitting publish on this blog, a few folks from the Perforce community shared a better way to output JSON data from p4 commands using the -ztag and -Mj flags: p4 -ztag -Mj changes -m3 | jq -s '.'

I recently ran across this blog showing how to use jq to format git log output to JSON. Curious about jq and wanting to try a similar thing with Perforce, I was able to modify the example pretty easily.

This is what p4 changes output looks like:

$ p4 changes

Change 3 on 2023/07/20 by grepliz@test-local-01 'Add day1.pdf'
Change 2 on 2023/07/20 by grepliz@test-local-01 'Add crazy.psd.'
Change 1 on 2023/07/20 by grepliz@test-local-01 'Populated server with files '
Enter fullscreen mode Exit fullscreen mode

Here is how you can format to JSON using jq:

$ p4 changes | jq -R -s '[split("\n")[:-1] | map(split("\u0020")) | .[] | { "change": .[1], "author": .[5], "date": .[3], "message": .[6:length] | join(" ") }]'

[
  {
    "change": "3",
    "author": "grepliz@test-local-01"
    "date": "2023/07/20",
    "message": "'Add day1.pdf'"
  },
  {
    "change": "2",
    "author": "grepliz@test-local-01",
    "date": "2023/07/20",
    "message": "'Add crazy.psd.'"
  },
  {
    "change": "1",
    "author": "grepliz@test-local-01",
    "date": "2023/07/20",
    "message": "'Populated server with files '"
  }
]
Enter fullscreen mode Exit fullscreen mode

I think the original blog I referred to gives a pretty good explanation of what is going on, but I find it helpful to write out what is going on for future reference (and for my future self!).

The input: p4 changes

I know the output of p4 changes is being piped into the jq program and it is helpful that the output is delimited by a space character and is one line per changelist. This output produces the columns needed for programs like jq and awk.

The flags: jq -R -s

The jq flags being used are nicely documented here.

--slurp/-s:
Instead of running the filter for each JSON object in the input, read the entire input stream into a large array and run the filter just once.

--raw-input/-R:
Don't parse the input as JSON. Instead, each line of text is passed to the filter as a string. If combined with --slurp, then the entire input is passed to the filter as a single long string.

The jq filter

'[split("\n")[:-1] | map(split("\u0020")) | .[] | { "change": .[1], "author": .[5], "date": .[3], "message": .[6:length] | join(" ") }]'

This is where all the magic happens. There are basically 4 sections to this filter.

  • split("\n")[:-1]

This says to split on newlines. If we were to stop here, we would get an array of the changelists.
Example:

p4 changes | jq -R -s 'split("\n")[:-1]'
[
  "Change 3 on 2023/07/20 by grepliz@test-local-01 'Add day1.pdf'",
  "Change 2 on 2023/07/20 by grepliz@test-local-01 'Add crazy.psd.'",
  "Change 1 on 2023/07/20 by grepliz@test-local-01 'Populated server with files '"
]
Enter fullscreen mode Exit fullscreen mode
  • map(split("\u0020"))

This tells the filter to map and split on the space character.
When combined with #1 above it would look like this:

p4 changes | jq -R -s 'split("\n")[:-1] | map(split("\u0020"))'

[
  [
    "Change",
    "3",
    "on",
    "2023/07/20",
    "by",
    "grepliz@test-local-01",
    "'Add",
    "day1.pdf'"
  ],
  [
    "Change",
    "2",
    "on",
    "2023/07/20",
    "by",
    "grepliz@test-local-01",
    "'Add",
    "crazy.psd.'"
  ],
  [
    "Change",
    "1",
    "on",
    "2023/07/20",
    "by",
    "grepliz@test-local-01",
    "'Populated",
    "server",
    "with",
    "files",
    "'"
  ]
]
Enter fullscreen mode Exit fullscreen mode
  • .[]

This creates arrays of the values such that we can iterate over it. The following is from the documentation:

Array/Object Value Iterator: .[]
If you use the .[index] syntax, but omit the index entirely, it will return all of the elements of an array. Running .[] with the input [1,2,3] will produce the numbers as three separate results, rather than as a single array.

You can also use this on an object, and it will return all the values of the object.

p4 changes | jq -R -s 'split("\n")[:-1] | map(split("\u0020")) | .[]'

[
  "Change",
  "3",
  "on",
  "2023/07/20",
  "by",
  "grepliz@test-local-01",
  "'Add",
  "day1.pdf'"
]
[
  "Change",
  "2",
  "on",
  "2023/07/20",
  "by",
  "grepliz@test-local-01",
  "'Add",
  "crazy.psd.'"
]
[
  "Change",
  "1",
  "on",
  "2023/07/20",
  "by",
  "grepliz@test-local-01",
  "'Populated",
  "server",
  "with",
  "files",
  "'"
]
Enter fullscreen mode Exit fullscreen mode
  • { "change": .[1], "author": .[5], "date": .[3], "message": .[6:length] | join(" ") }

Finally, this section gives us the desired JSON shape of each changelist and by wrapping the whole thing (the whole jq filter) with [], we get the final array.

AWS Security LIVE!

Join us for AWS Security LIVE!

Discover the future of cloud security. Tune in live for trends, tips, and solutions from AWS and AWS Partners.

Learn More

Top comments (0)

Heroku

Build apps, not infrastructure.

Dealing with servers, hardware, and infrastructure can take up your valuable time. Discover the benefits of Heroku, the PaaS of choice for developers since 2007.

Visit Site

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay