schibsted / jslt

JSON query and transformation language
Apache License 2.0
635 stars 120 forks source link

flatten example does not flatten arrays #349

Open samer1977 opened 5 months ago

samer1977 commented 5 months ago

Hi, I noticed the flatten-objects function here doesnt flatten nested arrays. I understand that flattening can have different meaning & expectations depending on each use case but my expectation when I want to flatten a json that it should cover everything. Of course considering arrays can get very complex given what arrays can hold. Part of learning jslt I wanted to take on this challenge to see if it can be done. I hope I was successful but part of doing this I found myself using some other helpful functions (see comments on each function) that I thought it might be nice to have not just for flattening but for other things and maybe have them as built function. Without further due , here is the code:

// This is different way of zipping where the key is the index itself. I found that provide more direct way if you need to access the index
// vs storing the index in another key.
def zip-by-index(json)

  let res = if(is-array($json)) [for(zip-with-index($json)) {string(.index):.value}] else []
  $res

//This is to make an array as complex object to create uniformity on how complex types in json can be processed. Im not sure there are 
// many use cases for this but its nice to have as an option
def objectify-array(array)
 let res = if($array==[] or $array==null) {}
          else if(size($array)==1) $array[0]
          else $array[-1]+objectify-array($array[0 : -1])
 $res 

def flatten_json(prefix,json)

   let simple=  {for($json) $prefix+.key:.value if(.key!=null and not(is-object(.value)) and not(is-array(.value)))}
   let complex= [for($json)  flatten_json($prefix+.key+".",.value) if(is-object(.value))]
   let array= [for($json)  flatten_json($prefix+.key+".",objectify-array(zip-by-index(.value))) if(is-array(.value))]

   objectify-array($array)+objectify-array($complex)+$simple

flatten_json("",.)

Example:

{
  "x": "x1",
  "y": "y2",
  "z": {
    "z1": "z11",
    "z2": null,
    "z3": [
      1,
      {
        "zzz": "skid",
        "zzz1": null
      },
      2
    ]
  }
}

Output:

{
  "x" : "x1",
  "y" : "y2",
  "z.z1" : "z11",
  "z.z3.0" : 1,
  "z.z3.2" : 2,
  "z.z3.1.zzz" : "skid"
}

I appreciate any well explained feedback. Thanks S

catull commented 5 months ago

This is not my original code, I stole it from here.

I changed it very lightly, it is not exactly what you want, because it does not process arrays, yet. See the unchanged z3 array below.

At any rate, here it is:

// JSLT transform which flattens nested objects into flat objects
// { "a": { "b": 1 } } => { "a.b": 1 }
def flatten-object (obj)
  let flat = { for ($obj) .key : .value if (not (is-object (.value))) }

  let nested = [
     for ($obj)
     let outerkey = (.key)
       [for (flatten-object (array (.value))) {
         "key": $outerkey + "." + .key,
         "value": if (is-object (.value)) flatten-object (.value) else .value
       }]
     if (is-object (.value))
  ]

  let flattened = (flatten ($nested))

  { for ($flattened) .key : .value } + $flat

flatten-object (.)

This transformation produces for the input above this result:

{
  "x" : "x1",
  "y" : "y2",
  "z.z1" : "z11",
  "z.z3" : [ 1, {
    "zzz" : "skid",
    "zzz1" : null
  }, 2 ]
}