HiveBrain v1.2.0
Get Started
← Back to all entries
patterncsharpModerate

Deep nesting when looping over an object model coming from a 3rd part

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
fromloopingnesting3rdpartmodeldeepcomingwhenobject

Problem

I am stuck with this horrible object model coming back from a 3rd party product. It's six levels of objects deep, and I have to loop through the collection in each level, to get the values I need, in order to produce a small object model that I actually require.

The code ends up looking like this (with variable and type names changed). How can I clean up this mess when I can't modify the structure of the rootObject?

(This is .NET 3.5.)

```
var levelOneEnumerator = rootObject.GetEnumerator();
while (levelOneEnumerator.MoveNext())
{
var levelOneItem = levelOneEnumerator.Current as Foo_LevelOneItem;
if (levelOneItem == null) continue;

var levelTwoItemsEnumerator = levelOneItem.LevelTwoItems.GetEnumerator();
while (levelTwoItemsEnumerator.MoveNext())
{
var LevelTwoItemsItem = levelTwoItemsEnumerator.Current as Foo_LevelTwoItem;
if (LevelTwoItemsItem == null) continue;

var foobars = new List();

var levelThreeItemsEnumerator = LevelTwoItemsItem.LevelThreeItems.GetEnumerator();
while (levelThreeItemsEnumerator.MoveNext())
{
var levelThreeItem = levelThreeItemsEnumerator.Current as Foo_LevelThreeItem;
if (levelThreeItem == null) continue;

var levelFourItemsEnumerator = levelThreeItem.LevelFourItems.GetEnumerator();
while (levelFourItemsEnumerator.MoveNext())
{
var levelFourItem = levelFourItemsEnumerator.Current as Foo_LevelFourItem;
if (levelFourItem == null) continue;

var levelFiveItemsEnumerator = levelFourItem.LevelFiveItems.GetEnumerator();
while (levelFiveItemsEnumerator.MoveNext())
{
var levelFiveItem = levelFiveItemsEnumerator.Current as Foo_LevelFiveItem;
if (levelFiveItem == null) continue;

var levelSixItemsEnumerator = levelFiveItem.LevelSixItems.GetEnumerator();
while

Solution

First of all, I would realise that

var levelOneEnumerator = rootObject.GetEnumerator();
while (levelOneEnumerator.MoveNext())
{
    var levelOneItem = levelOneEnumerator.Current as Foo_LevelOneItem;
    if (levelOneItem == null) continue;


is equivalent to the much shorter

foreach (var levelOneItem in rootObject.OfType())
{


Then you will realise that you have several nested foreach loops which make for nicely-readable single-line nestings. So factor all the multi-line code into methods of its own. The end-result I got looks like this:

foreach (var levelOneItem in rootObject.OfType())
    foreach (var levelTwoItem in levelOneItem.LevelTwoItems.OfType())
        yield return new FooBarsCollection
        {
            Prop1 = levelTwoItem.Prop1,
            Prop2 = levelTwoItem.Prop2,
            Prop3 = levelTwoItem.Prop3,
            FooBars = getFoobars(levelTwoItem)
        };

[...]

private static List getFoobars(Foo_LevelTwoItem levelTwoItem)
{
    var foobars = new List();

    foreach (var levelThreeItem in levelTwoItem.LevelThreeItems.OfType())
        foreach (var levelFourItem in levelThreeItem.LevelFourItems.OfType())
            foreach (var levelFiveItem in levelFourItem.LevelFiveItems.OfType())
                foreach (var levelSixItem in levelFiveItem.LevelSixItems.OfType())
                    processLevelSixItem(foobars, levelFiveItem, levelSixItem.Key);

    return foobars;
}

private static void processLevelSixItem(List foobars, Foo_LevelFiveItem levelFiveItem, Foo_LevelSixItemKey levelSixKey)
{
    var foobar = foobars.Where(x => x.Key == levelSixKey).FirstOrDefault();
    if (foobar == null)
    {
        foobar = new FooBar
        {
            LevelSixKey = levelSixKey,
            TransDate = levelFiveItem.TransDate,
            PaidAmount = 0
        };
        foobars.Add(foobar);
    }

    // * -1 because value should be positive, while product reports a negative (and vice versa)
    foobar.PaidAmount += (levelFiveItem.PaidAmount ?? 0) * -1;
}


Of course, you could further change this into a much more LINQy expression involving either SelectMany or the from query syntax, but to be honest, in your particular case I would leave it like this. It is very clear. In case you still want the query syntax, here is just getFoobars to give you the idea:

private static List getFoobars(Foo_LevelTwoItem levelTwoItem)
{
    var foobars = new List();

    var query =
        from levelThreeItem in levelTwoItem.LevelThreeItems.OfType()
        from levelFourItem in levelThreeItem.LevelFourItems.OfType()
        from levelFiveItem in levelFourItem.LevelFiveItems.OfType()
        from levelSixItem in levelFiveItem.LevelSixItems.OfType()
        select new { LevelFiveItem = levelFiveItem, Key = levelSixItem.Key };

    foreach (var info in query)
        processLevelSixItem(foobars, info.LevelFiveItem, info.Key);

    return foobars;
}

Code Snippets

var levelOneEnumerator = rootObject.GetEnumerator();
while (levelOneEnumerator.MoveNext())
{
    var levelOneItem = levelOneEnumerator.Current as Foo_LevelOneItem;
    if (levelOneItem == null) continue;
foreach (var levelOneItem in rootObject.OfType<Foo_LevelOneItem>())
{
foreach (var levelOneItem in rootObject.OfType<Foo_LevelOneItem>())
    foreach (var levelTwoItem in levelOneItem.LevelTwoItems.OfType<Foo_LevelTwoItem>())
        yield return new FooBarsCollection
        {
            Prop1 = levelTwoItem.Prop1,
            Prop2 = levelTwoItem.Prop2,
            Prop3 = levelTwoItem.Prop3,
            FooBars = getFoobars(levelTwoItem)
        };

[...]

private static List<FooBar> getFoobars(Foo_LevelTwoItem levelTwoItem)
{
    var foobars = new List<FooBar>();

    foreach (var levelThreeItem in levelTwoItem.LevelThreeItems.OfType<Foo_LevelThreeItem>())
        foreach (var levelFourItem in levelThreeItem.LevelFourItems.OfType<Foo_LevelFourItem>())
            foreach (var levelFiveItem in levelFourItem.LevelFiveItems.OfType<Foo_LevelFiveItem>())
                foreach (var levelSixItem in levelFiveItem.LevelSixItems.OfType<Foo_LevelSixItem>())
                    processLevelSixItem(foobars, levelFiveItem, levelSixItem.Key);

    return foobars;
}

private static void processLevelSixItem(List<FooBar> foobars, Foo_LevelFiveItem levelFiveItem, Foo_LevelSixItemKey levelSixKey)
{
    var foobar = foobars.Where(x => x.Key == levelSixKey).FirstOrDefault();
    if (foobar == null)
    {
        foobar = new FooBar
        {
            LevelSixKey = levelSixKey,
            TransDate = levelFiveItem.TransDate,
            PaidAmount = 0
        };
        foobars.Add(foobar);
    }

    // * -1 because value should be positive, while product reports a negative (and vice versa)
    foobar.PaidAmount += (levelFiveItem.PaidAmount ?? 0) * -1;
}
private static List<FooBar> getFoobars(Foo_LevelTwoItem levelTwoItem)
{
    var foobars = new List<FooBar>();

    var query =
        from levelThreeItem in levelTwoItem.LevelThreeItems.OfType<Foo_LevelThreeItem>()
        from levelFourItem in levelThreeItem.LevelFourItems.OfType<Foo_LevelFourItem>()
        from levelFiveItem in levelFourItem.LevelFiveItems.OfType<Foo_LevelFiveItem>()
        from levelSixItem in levelFiveItem.LevelSixItems.OfType<Foo_LevelSixItem>()
        select new { LevelFiveItem = levelFiveItem, Key = levelSixItem.Key };

    foreach (var info in query)
        processLevelSixItem(foobars, info.LevelFiveItem, info.Key);

    return foobars;
}

Context

StackExchange Code Review Q#931, answer score: 16

Revisions (0)

No revisions yet.