Jason Davis
Jason Davis

Reputation: 65

Groovy: How do iterate through a map to create a new map with values baed on a specific condition

I am in no way an expert with groovy so please don't hold that against me.

I have JSON that looks like this:

{
  "metrics": [
    {
      "name": "metric_a",
      "help": "This tracks your A stuff.",
      "type": "GAUGE",
      "labels": [
        "pool"
      ],
      "unit": "",
      "aggregates": [],
      "meta": [
        {
          "category": "CAT A",
          "deployment": "environment-a"
        }
      ],
      "additional_notes": "Some stuff (potentially)"
    },
    ...
  ]
  ...
}

I'm using it as a source for automated documentation of all the metrics. So, I'm iterating through it in various ways to get the information I need. So far so good, I'm most of the way there. The problem is this all needs to be organized per the deployment environment. Meaning, multiple metrics will share the same value for deployment.

My thought was I could create a map with deployment as the key and the metric name for any metric that has a matching deployment as the value. Once I have that map, it should be easy for me to organize things the way they should be. I can't figure out how to do that. The result is all the metric names are added which is expected since I'm not doing anything to filter them out. I was thinking that groupBy would make sense here but I can't figure out how to use it effectively and frankly I'm not sure it will solve my problem by itself. Here is my code so far:

parentChild = [:]
children = []

metrics.each { metric ->
   def metricName = metric.name
   def depName = metric.meta.findResult{ it.deployment }
   children.add(metricName)
   parentChild.put(depName, children)
}

What is the best way to create a new map where the values for each key are based off a specific condition?

EDIT: The desired result would be each key in the resulting map would be a unique deployment value from all the metrics (as a string). Each value would be name of each metric that contains that deployment (as an array).

[environment-a:
   [metric_a,metric_b,metric_c,...], 
environment-b:
   [metric_d,metric_e,metric_f,...]
...]

Upvotes: 1

Views: 276

Answers (1)

injecteer
injecteer

Reputation: 20699

I would use a combo of withDefault() to pre-fill each map-entry value with a fresh TreeSet-instance (sorted no-duplicates set) and standard inject().

I reduced your sample data to the bare minimum and added some new nodes:

import groovy.json.*

String input = '''\
{
  "metrics": [
    {
      "name": "metric_a",
      "meta": [
        {
          "deployment": "environment-a"
        }
      ]
    },
    {
      "name": "metric_b",
      "meta": [
        {
          "deployment": "environment-a"
        }
      ]
    },
    {
      "name": "metric_c",
      "meta": [
        {
          "deployment": "environment-a"
        },
        {
          "deployment": "environment-b"
        }
      ]
    },
    {
      "name": "metric_d",
      "meta": [
        {
          "deployment": "environment-b"
        }
      ]
    }   
  ]
}'''

def json = new JsonSlurper().parseText input

def groupedByDeployment = json.metrics.inject( [:].withDefault{ new TreeSet() } ){ res, metric ->
  metric.meta.each{ res[ it.deployment ] << metric.name }
  res
}

assert groupedByDeployment.toString() == '[environment-a:[metric_a, metric_b, metric_c], environment-b:[metric_c, metric_d]]'

If your metrics.meta array is supposed to have a single value, you can simplify the code by replacing the line:

metric.meta.each{ res[ it.deployment ] << metric.name }

with

res[ metric.meta.first().deployment ] << metric.name

Upvotes: 1

Related Questions