'JSON jq/python file manipulation with specific key name aggregation

I need to modify the structure of this json file:

[
   {
      "id":"3333",
      "properties":{
         "label":"Computer",
         "name":"My-Laptop"
      }
   },
   {
      "id":"9998",
      "type":"file_system",
      "properties":{
         "mount_point":"/opt",
         "name":"/dev/mapper/rhel-opt",
         "root_container":"3333"
      },
      "label":"FileSystem"
   },
   {
      "id":"9999",
      "type":"file_system",
      "properties":{
         "mount_point":"/var",
         "name":"/dev/mapper/rhel-var",
         "root_container":"3333"
      },
      "label":"FileSystem"
   }
]

in order to have this kind of output:

[
   {
      "id":"3333",
      "properties":{
         "label":"Computer",
         "name":"My-Laptop",
         "file_system":[
            "/opt",
            "/var"
         ]
      }
   }
]

The idea is to have, in the new json structure, the visibility of my laptop with the two file-system partition in an array named "file_system". As you can see the two partition are related to the first by the id and root_container. So, imagine to have not only one laptop, bat thousands of laptop, with different id and every one of these have different partition, related to the laptop by the root_container key.

Is there an option to do this with jq functions or python script? Many thanks



Solution 1:[1]

You could employ reduce to iterate over the items while extracting their id, mount_point and root_container. Then, if a root_container was present, delete that entry and add its mount_point to the entry whose id matches their root_container. For convenience, I also employed INDEX on the items' id fields to simplify their access as .[$id] and .[$root_container], which had to be undone at the end using map(.).

jq '
  reduce .[] as {$id, properties: {$mount_point, $root_container}} (
    INDEX(.id);
    if $root_container then
      del(.[$id])
      | .[$root_container].properties.file_system += [$mount_point]
    else . end
  )
  | map(.)
'
[
  {
    "id": "3333",
    "properties": {
      "label": "Computer",
      "name": "My-Laptop",
      "file_system": [
        "/opt",
        "/var"
      ]
    }
  }
]

Demo

Solution 2:[2]

You can dump request into a buffer, modify the buffer (with regexp or replace), and send modified buffer to the host using net.Dial.

Example:

package main

import (
    "bufio"
    "crypto/tls"
    "fmt"
    "log"
    "net/http"
    "net/http/httputil"
    "strings"
)

func main() {

    // create and dump request

    req, err := http.NewRequest(http.MethodGet, "https://golang.org", nil)
    if err != nil {
        log.Fatal(err)
    }

    req.Header.Add("User-Agent", "aaaaa")

    buf, err := httputil.DumpRequest(req, true)
    if err != nil {
        log.Fatal(err)
    }

    // Corrupt request

    str := string(buf)
    str = strings.Replace(str, "User-Agent: aaaaa", "UsEr-AgEnT: aaa\"aaa", 1)
    println(str)

    // Dial and send raw request text

    conn, err := tls.Dial("tcp", "golang.org:443", nil)
    if err != nil {
        log.Fatal(err)
    }
    defer conn.Close()

    fmt.Fprintf(conn, str)

    // Read response

    br := bufio.NewReader(conn)
    resp, err := http.ReadResponse(br, nil)
    if err != nil {
        log.Fatal(err)
    }
    log.Printf("%+v", resp)
}

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 pmf
Solution 2 serge-v