Search code examples
keyeditjqrecursive-descent

Descend JSON doc paths and update field names with jq


I'd like to descend, to any depth, all the paths in a MongoDB JSON doc and possibly modify each field name using jq (mongo shell doesn't seem capable of doing this.). Specifically, I'd like to remove all underscores ("_") in all field names except for "_id". I've tried various combinations of recurse and with_entries without success. So, e.g., transform this:

{
  "_id": "doc1",
  "field_11": "value_11",
  "field_12": {
    "field_121": {
      "field_1211": "value_1211",
      "field_1212": "value_1212"
    },
    "field_122": {
      "field_1221": "value_1221",
      "field_1222": "value_1222"
    }
  },
  "field_13": [
    {
      "field_131": {
        "field_1311": "value_1311",
        "field_1312": "value_1312"
      },
      "field_132": {
        "field_1321": "value_1321",
        "field_1322": "value_1322"
      }
    }
  ],
  "field_one_four": "value_one_four",
  "Field_One_Five": "Value_One_Five"
}

to this:

{
  "_id": "doc1",
  "field11": "value_11",
  "field12": {
    "field121": {
      "field1211": "value_1211",
      "field1212": "value_1212"
    },
    "field122": {
      "field1221": "value_1221",
      "field1222": "value_1222"
    }
  },
  "field13": [
    {
      "field131": {
        "field1311": "value_1311",
        "field1312": "value_1312"
      },
      "field132": {
        "field1321": "value_1321",
        "field1322": "value_1322"
      }
    }
  ],
  "fieldonefour": "value_one_four",
  "FieldOneFive": "Value_One_Five"
}

Bonus: What if I want to remove underscores and convert compound field names to camelcase? I.e., "field_one_four" would become "fieldOneFour" and "Field_One_Five" would become "fieldOneFive".


Solution

  • The simplest is to use walk:

    walk( if type == "object"
          then with_entries( if .key != "_id" then .key |= gsub("_";"") else . end ) 
          else . end)
    

    If your jq does not have walk, you can easily find its def by googling: jq "def walk"

    Bonus

    You can use the following helper function:

    def camelcase:
      def u:  if 97 <= . and . <= 122 then . - 32  else . end;
      def c:
        if length <= 1 then
          if .[0] == 95 then [] else . end
        else if .[0] == 95 then [.[1]|u] + (.[2:] | c) else [.[0]] + (.[1:]|c) end
        end;
    
      explode | c | implode;
    

    Extra bonus

    def camel_Case:
      if test("_")
      then .[0:1] as $first
      | if $first | (. ==  ascii_downcase)
        then $first | ascii_upcase + ([2:]|camelcase)
        else camelcase
        end
      else .
      end ;