Anton Romanov
Anton Romanov

Reputation: 25

jq: how to replace keys with values ​from other keys whose ​are in strings of some array

Consider following array:

{
"A": 100, 
"B": 200, 
"C": "ccc",
"arr": [
  {
    "result": ".R1.R3", 
    "fmt": "%s::%s::baz", 
    "vals": [".A", ".B"]
  },
  {
    "result": ".R2.R4", 
    "fmt": "%s/%s", 
    "vals": [".A", ".C"]
  }
]
}

I need to replace keys according some format with values ​​from other keys whose ​​are in strings of some array.

Desired output:

{
"A": 100, 
"B": 200, 
"C": "ccc",
"R1": {"R3": "100::200::baz"},
"R2": {"R4": "100/ccc"}
}

Upvotes: 0

Views: 236

Answers (1)

pmf
pmf

Reputation: 36033

You didn't specify what language you use in the .vals array items to reference into the document. If you were trying to execute (arbitrary) jq code from there, know that jq cannot do that with code provided as a string value. jq also doesn't provide printf-style substitutions using %s (and others). Therefore, you either need to re-implement a whole bunch of (third-party) functionality, or revert to a simpler scheme describing your references and substitutions.

For the sake of simplicity, this solution just removes the first character (the dot) from the .vals array items and treats the result as top-level field name, and then simply replaces each occurrence of a literal %s with the next value. This should give you an overview of the general technique.

. as $top | reduce .arr[] as $a (del(.arr); .[$a.result] = (
  reduce $a.vals[][1:] as $val ($a.fmt; sub("%s"; $top[$val] | @text))
))
{
  "A": 100,
  "B": 200,
  "C": "ccc",
  ".R1": "100::200::baz",
  ".R2": "100/ccc"
}

Demo


One quite simple way of improving the reference language is to instead use path expressions jq provides functions for. They are represented as arrays with field names as strings items and array indices as number items. .A would become ["A"], .A[3].B would become ["A",3,"B"], and so on. Thus, assume your input looked like this:

{
  "A": 100,
  "B": 200,
  "C": "ccc",
  "arr": [
    {
      "result": ".R1",
      "fmt": "%s::%s::baz",
      "vals": [["A"], ["B"]]
    },
    {
      "result": ".R2",
      "fmt": "%s/%s",
      "vals": [["A"], ["C"]]
    }
  ]
}

Then you could use getpath to evaluate the given path expressions as above:

. as $top | reduce .arr[] as $a (del(.arr); .[$a.result] = (
  reduce $a.vals[] as $path ($a.fmt; sub("%s"; $top | getpath($path) | @text))
))
{
  "A": 100,
  "B": 200,
  "C": "ccc",
  ".R1": "100::200::baz",
  ".R2": "100/ccc"
}

Demo


Edit: As the question has been modified with the .result value now also being subject to reference interpretation, measures taken for .vals therefore apply to it as well. This implies changing the suggested source document format to use path expressions as in "result": ["R1", "R3"], and changing the assignment in the suggested code from .[$a.result] = ... to setpath($a.result; ...):

{
  "A": 100,
  "B": 200,
  "C": "ccc",
  "arr": [
    {
      "result": ["R1", "R3"],
      "fmt": "%s::%s::baz",
      "vals": [["A"], ["B"]]
    },
    {
      "result": ["R2", "R4"],
      "fmt": "%s/%s",
      "vals": [["A"], ["C"]]
    }
  ]
}
. as $top | reduce .arr[] as $a (del(.arr); setpath($a.result;
  reduce $a.vals[] as $path ($a.fmt; sub("%s"; $top | getpath($path) | @text))
))
{
  "A": 100,
  "B": 200,
  "C": "ccc",
  "R1": {
    "R3": "100::200::baz"
  },
  "R2": {
    "R4": "100/ccc"
  }
}

Demo

Upvotes: 1

Related Questions