Andy Kovtun
Andy Kovtun

Reputation: 121

Traversing/modifying a Groovy data structure

I'm developing a project that would read certain data in proprietary format, gather it into some unified Groovy data structure, and write it out in a neat XML (or, JSON, haven't decided yet) format. I'm a total newbie to Groovy, but, the project being Ant build project, Groovy seems the best way to go.

So far so good, I'm able to read data, create atomic structures, concatenate them using >> operator, and seamlessly dump them to XML with MarkupBuilder (ways easier than if I were doing it in Java). However, I'm stuck now at the point when I need to slightly modify the gathered structures, or traverse through them to compose some aggregated data.

To illustrate, supposing we collected our data so it's equivalent to:

def inventory = {
    car (make: "Subaru", model: "Impreza WRX", year: 2010, color: "Blue") {
        feature ("Premium sound")
        feature ("Brembo brakes")
        bug ("Leaks oil")
        bug ("Needs new transmission")
    }
    car (make: "Jeep", model: "Wrangler", year: 13, awd: true) {
        feature ("Soft top")
        bug ("Doesn't start")
        bug ("Flooded")
    }
    // blahblahblah
}

and we're trying to achieve following, for example:

  1. Remove all "bug" items (supposing, we're composing a list for publishing on our dealership's website). Or, all "feature"'s (if it's for our pre-sale repairs squad).
  2. Go through the list and make sure all "year" attributes are 4-digit
  3. Obsolete all "awd" attributes, moving them to "feature" list

so we end up with a structure like this:

def inventory = {
    car (make: "Subaru", model: "Impreza WRX", year: 2010, color: "Blue") {
        feature ("Premium sound")
        feature ("Brembo brakes")
    }
    car (make: "Jeep", model: "Wrangler", year: 2013) {
        feature ("AWD")
        feature ("Soft top")
    }
    // blahblahblah
}

Actually, I'm OK with going through the original structure composing a new list (my data isn't that huge to require in-place editing), but how do I traverse through this structure, in the first place?

Oh, and a question of terminology. Maybe, I was just googling around for a wrong keyword... This entity as defined in code: is it called "closure" too, or there's a different term for it?

Upvotes: 1

Views: 696

Answers (1)

Beryllium
Beryllium

Reputation: 12998

Looking at inventory, it could actually being represented by something like

@Immutable(copyWith = true)
class Car {
    String make
    String model
    int year
    String color
    boolean awd

    List<String> features
    List<String> bugs
}

As you are fine with composing a new list (and there are generally speaking few reasons not to do so), you can add the @Immutable annotation. As a "side effect" you can add copyWith = true to get a copy method.

The definition of the inventory could look like this:

    def inventory = [
        new Car(
            make: "Subaru",
            model: "Impreza WRX",
            year: 2010,
            color: "Blue",
            features: ["Premium sound", "Brembo brakes"],
            bugs: ["Leaks oil", "Needs new transmission"]),
        new Car(
            make: "Jeep",
            model: "Wrangler",
            year: 13,
            awd: true,
            features: ["Soft top"],
            bugs: ["Doesn't start", "Flooded"])
        ]

which gives you an immutable represenation of your data. Then you can traverse and change your data using the collection API. In your case:

    def result = inventory
        .collect { it.copyWith(bugs: []) }
        .collect { it.copyWith(year: 
                     it.year < 2000 ? it.year + 2000 : it.year) }
        .collect {
            if (it.awd) { 
                it.copyWith(features: it.features + "AWD")
            } else {
                it
            } 

Each transformation is applied individually (and there are only immutable instances flowing through that pipeline).


If you really want to remove the the empty bugs list and/or the awd property, define the target class:

class FixedCar {
    String make
    String model
    int year
    String color

    List<String> features

    static FixedCar apply(Car car) {
        new FixedCar(
            make: car.make,
            model: car.model,
            year: car.year,
            color: car.color,
            features: car.features)
    }
}

and add another call to collect:

    def result = inventory
        .collect { it.copyWith(bugs: []) }
        .collect { it.copyWith(year: 
                     it.year < 2000 ? it.year + 2000 : it.year) }
        .collect {
            if (it.awd) { 
                it.copyWith(features: it.features + "AWD")
            } else {
                it
            } 
        }.collect { FixedCar.apply(it) }

Upvotes: 1

Related Questions