Home > database >  Merge two nested JSON files using JQ
Merge two nested JSON files using JQ

Time:04-18

I'm trying to merge two JSON files. The main destination is to overwrite environment variables in the 1st file with environment variables in the 2nd.

1st file:

{
    "containerDefinitions": [
        {
            "name": "foo",
            "image": "nginx:latest",
            "cpu": 1024,
            "memory": 4096,
            "memoryReservation": 2048,
            "portMappings": [
                {
                    "containerPort": 8080,
                    "hostPort": 0,
                    "protocol": "tcp"
                }
            ],
            "essential": true,
            "environment": [
                {
                    "name": "SERVER_PORT",
                    "value": "8080"
                },
                {
                    "name": "DB_NAME",
                    "value": "example_db"
                }
            ],
            "mountPoints": [],
            "volumesFrom": [],
            "logConfiguration": {
                "logDriver": "awslogs",
                "options": {
                    "awslogs-group": "/dev/ecs/example",
                    "awslogs-region": "us-west-1",
                    "awslogs-stream-prefix": "ecs"
                }
            }
        }
    ],
    "family": "bar",
    "taskRoleArn": "arn:aws:iam::111111111111:role/assume-ecs-role",
    "executionRoleArn": "arn:aws:iam::111111111111:role/ecs-task-execution-role",
    "networkMode": "bridge",
    "volumes": [],
    "placementConstraints": [],
    "requiresCompatibilities": [
        "EC2"
    ]
}

2nd file:

{
    "containerDefinitions": [
        {
            "environment": [
                {
                    "name": "SERVER_PORT",
                    "value": "8081"
                }
            ]
        }
    ]
}

The product has to be next:

{
    "containerDefinitions": [
        {
            "name": "foo",
            "image": "nginx:latest",
            "cpu": 1024,
            "memory": 4096,
            "memoryReservation": 2048,
            "portMappings": [
                {
                    "containerPort": 8080,
                    "hostPort": 0,
                    "protocol": "tcp"
                }
            ],
            "essential": true,
            "environment": [
                {
                    "name": "SERVER_PORT",
                    "value": "8081"
                },
                {
                    "name": "DB_NAME",
                    "value": "example_db"
                }
            ],
            "mountPoints": [],
            "volumesFrom": [],
            "logConfiguration": {
                "logDriver": "awslogs",
                "options": {
                    "awslogs-group": "/dev/ecs/example",
                    "awslogs-region": "us-west-1",
                    "awslogs-stream-prefix": "ecs"
                }
            }
        }
    ],
    "family": "bar",
    "taskRoleArn": "arn:aws:iam::111111111111:role/assume-ecs-role",
    "executionRoleArn": "arn:aws:iam::111111111111:role/ecs-task-execution-role",
    "networkMode": "bridge",
    "volumes": [],
    "placementConstraints": [],
    "requiresCompatibilities": [
        "EC2"
    ]
}

I tried to do next:

jq -s 'reduce .[] as $item ({}; reduce ($item | keys_unsorted[]) as $key (.; $item[$key] as $val | ($val | type) as $type | .[$key] = if ($type == "array") then (.[$key]   $val | unique) elif ($type == "object") then (.[$key]   $val) else $val end))'  1.json 2.json

But the result is:

{
  "containerDefinitions": [
    {
      "name": "foo",
      "image": "nginx:latest",
      "cpu": 1024,
      "memory": 4096,
      "memoryReservation": 2048,
      "portMappings": [
        {
          "containerPort": 8080,
          "hostPort": 0,
          "protocol": "tcp"
        }
      ],
      "essential": true,
      "environment": [
        {
          "name": "SERVER_PORT",
          "value": "8080"
        },
        {
          "name": "DB_NAME",
          "value": "example_db"
        }
      ],
      "mountPoints": [],
      "volumesFrom": [],
      "logConfiguration": {
        "logDriver": "awslogs",
        "options": {
          "awslogs-group": "/dev/ecs/example",
          "awslogs-region": "us-west-1",
          "awslogs-stream-prefix": "ecs"
        }
      }
    },
    {
      "environment": [
        {
          "name": "SERVER_PORT",
          "value": "8081"
        }
      ]
    }
  ],
  "family": "bar",
  "taskRoleArn": "arn:aws:iam::111111111111:role/assume-ecs-role",
  "executionRoleArn": "arn:aws:iam::111111111111:role/ecs-task-execution-role",
  "networkMode": "bridge",
  "volumes": [],
  "placementConstraints": [],
  "requiresCompatibilities": [
    "EC2"
  ]
}

Could anyone help to find out how to reach the right result?

CodePudding user response:

Something like this will do the trick:

 (input | .containerDefinitions[0].environment | from_entries) as $new_env
| input | .containerDefinitions[].environment |= ((from_entries   $new_env) | to_entries)

Online demo


In case it's unclear, the invocation should look like so:

jq -n '...' 2.json 1.json

CodePudding user response:

Here's a solution using tostream and has(1) to read the values from the second file, and setpath to set them in the first file:

jq 'reduce (input | tostream | select(has(1))) as $i (.; setpath($i[0]; $i[1]))' \
  1.json 2.json

Demo


When providing the files in reversed order (2.json 1.json), the context . and input have to be swapped:

jq 'reduce (tostream | select(has(1))) as $i (input; setpath($i[0]; $i[1]))' \
  2.json 1.json

Demo

  • Related