patterncsharpModerate
Deep nesting when looping over an object model coming from a 3rd part
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
(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
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
is equivalent to the much shorter
Then you will realise that you have several nested
Of course, you could further change this into a much more LINQy expression involving either
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.