Skip to content

Instantly share code, notes, and snippets.

@reegnz
Created June 19, 2020 10:39
Show Gist options
  • Save reegnz/5bceb53427008a4ff9367eb8eae97b85 to your computer and use it in GitHub Desktop.
Save reegnz/5bceb53427008a4ff9367eb8eae97b85 to your computer and use it in GitHub Desktop.
CamelCase <--> snake_case conversion in jq

CamelCase <--> snake_case conversion with jq

tl;dr

I provide you with 3 jq lib functions that will help you in converting between snake_case and CamelCase.

The interface

I want to change keys in my json from camelcase to snake_case.

{
  "SomeKey": {
    "NestedKey": "NestedValue",
    "NestedList": [
      {
        "NestedKey": "NestedValue"
      },
      {
        "NestedKey": "NestedValue"
      },
      {
        "NestedKey": "NestedValue"
      }
    ]
  }
}

It would be nice to do this with a simple command, something like this:

<my.json | jq 'map_keys(camel_to_snake)' > my_snake.json

This would result in the following output:

{
  "some_key": {
    "nested_key": "NestedValue",
    "nested_list": [
      {
        "nested_key": "NestedValue"
      },
      {
        "nested_key": "NestedValue"
      },
      {
        "nested_key": "NestedValue"
      }
    ]
  }
}

We also expect that we get the same CamelCase json back if we convert back the snake_case version.

diff <(<my.json jq) <(<my.json | jq 'map_keys(camel_to_snake)|map_keys(snake_to_camel)'); echo $?
0

The solution

Once you put the following functions into $HOME/.jq file the above command will just work!

def map_keys(mapper):
  walk(
    if type == "object"
    then
      with_entries({
        key: (.key|mapper),
	value
      })
    else .
    end
  );

def camel_to_snake:
  [
    splits("(?=[A-Z])")
  ]
  |map(
    select(. != "")
    | ascii_downcase
  )
  | join("_");

def snake_to_camel:
  split("_")
  | map(
    split("")
    | .[0] |= ascii_upcase
    | join("")
  )
  | join("");
@qiangli
Copy link

qiangli commented Oct 17, 2022

Thanks! You rock!

@reegnz
Copy link
Author

reegnz commented Oct 20, 2022

In case people are interested, I have made other jq functions as well, for converting from/to camel, snake, kebab, dotted, etc. cases as well: https://github.com/reegnz/dotfiles/tree/master/jq

@opsb
Copy link

opsb commented Feb 25, 2024

Case changing and a great pattern for adding utility functions to jq, thank you so much!

@enlightenedpie
Copy link

enlightenedpie commented Sep 12, 2024

I realize this is fairly old now, but I just discovered it via Google. However, one thing... Camel case and Pascal case are not the same.

Pascal case starts with uppercase letter: "PascalCase"

Camel case starts with lowercase letter: "camelCase"

Your snake_to_camel function outputs Pascal case. How could this be extended to avoid capitalizing the first word in the split?

EDIT: I figured out a solution

def snake_to_pascal:
  split("_")
  | map(
    split("")
    | .[0] |= ascii_upcase
    | join("")
  )
  | join("");
  
def snake_to_camel:
  split("_")
  | map(
    split("")
    | .[0] |= ascii_upcase
    | join("")
  )
  | .[0] |= ascii_downcase 
  | join("");

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment