user626912
user626912

Reputation: 2560

Avoiding many levels of loop nesting

I have a object graph which goes like this:

root
    : childs (array)
        : childs (array)

I am building a JSON response out of this so I need to loop through each collection creating code like this:

// code for root

// loop through direct root childs
for (Child child : childs) {

    // Loop through the childs of the object in current context.
    for (AnotherChild anotherChild : moreChilds) {

    }
}

How do you avoid such code? It will be an arrow in the end. I could have created own methods for each level of for loop, but is that a good approach? Are there other approaches which is better?

Upvotes: 0

Views: 1174

Answers (4)

Michał Ziober
Michał Ziober

Reputation: 38690

You could provide interface which all interested class should implement. That interface should provide method to converting a current object to JSON. See example:

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class JsonProgram {

    public static void main(String[] args) {
        Root root = new Root(Arrays.asList(new Child(Arrays.asList(
                new AnotherChild(1), new AnotherChild(2)))));
        System.out.println(root.toJSON());
    }

}

interface JsonState {
    String toJSON();
}

class Root implements JsonState {

    private List<Child> childs = new ArrayList<Child>();

    public Root(List<Child> childs) {
        this.childs = childs;
    }

    @Override
    public String toJSON() {
        StringBuilder builder = new StringBuilder();
        builder.append("{").append("\"childs\"").append(":[");
        int index = 0;
        for (Child child : childs) {
            builder.append(child.toJSON());
            if (index < childs.size() - 1) {
                builder.append(",");
            }
            index++;
        }
        builder.append("]\"}");
        return builder.toString();
    }
}

class Child implements JsonState {

    private List<AnotherChild> anotherChilds = new ArrayList<AnotherChild>();

    public Child(List<AnotherChild> anotherChilds) {
        this.anotherChilds = anotherChilds;
    }

    @Override
    public String toJSON() {
        StringBuilder builder = new StringBuilder();
        builder.append("{").append("\"anotherChilds\"").append(":[");
        int index = 0;
        for (AnotherChild child : anotherChilds) {
            builder.append(child.toJSON());
            if (index < anotherChilds.size() - 1) {
                builder.append(",");
            }
            index++;
        }
        builder.append("]}");
        return builder.toString();
    }
}

class AnotherChild implements JsonState {

    private int value;

    public AnotherChild(int value) {
        this.value = value;
    }

    @Override
    public String toJSON() {
        StringBuilder builder = new StringBuilder();
        builder.append("{").append("\"value\"").append(":\"").append(value)
                .append("\"}");
        return builder.toString();
    }
}

Output:

{
   "childs":[
      {
         "anotherChilds":[
            {
               "value":"1"
            },
            {
               "value":"2"
            }
         ]
      }
   ]
}

But it is not a good solution. Instead of implementing Your own solution You should use some library which can do it for You. I recommend to You google-gson. For me is the best.

EDIT - GSON EXAMPLE

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;

public class JsonProgram {

    public static void main(String[] args) {
        Root root = new Root(Arrays.asList(new Child(Arrays.asList(
                new AnotherChild(1), new AnotherChild(2)))));

        Gson gson = new GsonBuilder().serializeNulls().create();
        System.out.println(gson.toJson(root));
    }
}

class Root {

    private List<Child> childs = new ArrayList<Child>();

    public Root(List<Child> childs) {
        this.childs = childs;
    }

    @Override
    public String toString() {
        return Arrays.toString(childs.toArray());
    }
}

class Child {

    private List<AnotherChild> anotherChilds = new ArrayList<AnotherChild>();

    public Child(List<AnotherChild> anotherChilds) {
        this.anotherChilds = anotherChilds;
    }

    @Override
    public String toString() {
        return Arrays.toString(anotherChilds.toArray());
    }
}

class AnotherChild {

    private int value;

    public AnotherChild(int value) {
        this.value = value;
    }

    @Override
    public String toString() {
        return Integer.toString(value);
    }
}

Above example create same output. For me this is a more elegant solution.

Upvotes: 1

CapelliC
CapelliC

Reputation: 60034

That's a recursive structure, then you should use recursion to handle nesting. A depth first visit should do.

edit to interface JSON you would really follow the advice by @Mite Mitreski, for a recursive visit pseudocode sample:

void visit(Child tree) {
  json_write_class(tree);
  for (Attribute a : tree.attributes) {
    json_write_attr(a);
  if (tree.children != null) {
    json_push_indent();
    for (Child child : tree.children) {
      visit(child);
    }
    json_pop_indent();
  }
}

If you need more control, you could write kind of 'semantic actions' on nodes of that tree to establish the attributes, and implement the visitor pattern to output the data (more verbose than the first alternative).

Frequently helps to use the analogy of grammars and syntax trees, these are the most obvious sample we (as programmers) are used to.

Upvotes: 2

Augusto
Augusto

Reputation: 29997

I think you have a nasty design issue there, as the class that is doing all those loops knows a hell lot of the other classes (and thus breaking the Law of Demeter).

An approach I try to use (that I've learn from some very experienced developers) is to wrap collections (or arrays) in their own classes; and then create methods that iterate over the array/collection performing one operation. In this case, it could be calling another method in another class that wraps a collection.

In this way, each class has very little knowledge of what the other classes do (or the internals of the child objects).


Edit

Here's an example. Imagine that you have an account in a website similar to amazon. In that account, you have associated a few credit cards.

So, instead of having

class Account {
    List<CreditCard> creditCards;

    public CreditCard getPrimaryCard() {
        //complex code to find the primary credit card
    }
    //lots of other code related to the account and credit cards
}

you can do

class Account {
    CreditCards creditCards;

    public CreditCard getPrimaryCard() {
        creditCards.getPrimaryCard()
    }
    //lots of other code related to the account
}

class CreditCards {
    List<CreditCard> creditCards;

    public CreditCard getPrimaryCard() {
        //complex code to find the primary credit card
    }
    public void addCard(CreditCard creditCard) {
        //complex logic to validate that the card is not duplicated.
    }
    //lots of other code related to credit cards
}

In this way, Account doesn't need to know about how the creditCards are stored in memory (should it be a list? or a set? or get it from a remote webservice?)

Please bear in mind that this is a trivial example.

Upvotes: 1

Mite Mitreski
Mite Mitreski

Reputation: 3626

If we are talking about this specific problem (building a JSON response) you use some kind of serializer like jackson or write a custom one. There is a relevant question on this topic https://stackoverflow.com/questions/338586/a-better-java-json-library

On the other hand for some other uses you can use a more functional approach like Guava or Lambdaj.

But when it comes done to big O complexity these are not much of a help there, so you may wanna try different approach if possible then.

Upvotes: 3

Related Questions