5
IList<Customer> Customers =
            flat.GroupBy(cust => new { cust.ReferenceNumber, cust.Name, cust.Address })
                .Select(c => new Customer()
                {
                    ReferenceNumber = c.Key.ReferenceNumber,
                    Name = c.Key.Name,
                    Address = c.Key.Address,
                    Orders = c.Select(o => new Order()
                    {
                        OrderId = o.OrderId,
                        ProductName = o.ProductName,
                        Description = o.Description,
                        Amount = o.Amount
                    }).ToList()
                }).ToList()

Is taking a flat list and converting it into a nested object possible in Javascript? A generic solution that is??

1

2 Answers 2

19

Even though this is an old question, I thought I'd offer a more elegant solution:

/**
 * Groups an array of objects by one or more keys
 * 
 * @param array arr       The array of objects to group
 * 
 * @param string|function A string representing the child property to group by
 *                        or a function that returns an array of one or more properties.
 * 
 * @returns               An object with keys representing the grouping properties, 
 *                        finally holding an array of records that fell into 
 *                        those groups.
 */
var group = function( items, by ) {
    var groups = {},
        group,
        values,
        i = items.length,
        j,
        key,
        group_keys;

    // make sure we specified how we want it grouped
    if( !by ) { return items; }
    while( i-- ) { 

        // find out group values for this item
        values = ( typeof(by) === "function" && by( items[i] ) || 
                   typeof items[i] === "object" && items[i][by] || 
                   items[i] );

        // make sure our group values are an array
        values = values instanceof Array && values || [ values ];

        // recursively group
        group = groups;
        for( j = 0; j < values.length; j++ ) {
            key = values[j];
            group = ( group [key] || ( group [key] = j === values.length - 1 && [] || {} ) );
        }

        // for the last group, push the actual item onto the array
        group = ( group instanceof Array && group || [] ).push( items[i] );
    }

    return groups;
};

Calling it with this:

var items = [
    { "id" : 1, "name" : "foo",  "category" : "a" },
    { "id" : 2, "name" : "foo",  "category" : "a" },
    { "id" : 3, "name" : "bar",  "category" : "b" },
    { "id" : 4, "name" : "free", "category" : "a" },
    { "id" : 5, "name" : "beer", "category" : "b" },
    { "id" : 6, "name" : "foo",  "category" : "b" }
];

var groups = group( items, function( item ) { return [ item.category, item.name ]; } );

Yields this:

{
    b: {
        foo: [
            {
                id: 6
                name: foo
                category: b
            }
        ]
        beer: [
            {
                id: 5
                name: beer
                category: b
            }
        ]
        bar: [
            {
                id: 3
                name: bar
                category: b
            }
        ]
    }
    a: {
        free: [
            {
                id: 4
                name: free
                category: a
            }
        ]
        foo: [
            {
                id: 2
                name: foo
                category: a
            }
            {
                id: 1
                name: foo
                category: a
            }
        ]
    }
}

Anyway, hope this helps someone.

Sign up to request clarification or add additional context in comments.

4 Comments

+1 for the script! I was trying to implement it by myself with no luck :P ...it's too cryptic in my opinion, but it works, anyway I replaced "items" with "arr" to make it working properly. Thanks ;)
You are most welcome! Yes, the cryptic nature of this function allows for nested grouping (e.g. group by category, then by name). Recursion is our friend and makes this easy.
Excellent! And easily made as a jquery plugin, thank you! I made the arr/items correction to your answer, it should be fine now.
"I thought I'd offer a more elegant solution" -- or, well, a solution. Because the accepted answer is anything but one.
2

Yes.

You can pass functions around in JavaScript, which would serve the same purpose as the lambda expressions in the C# code. Take the use of JQuery's "each" as an example:

$('div.several').each(function() { 
  // Do something with the current div.
});

You can also create nested objects quite easily:

var outer = {
    inner1: {
        val1: 'a',
        val2: 'b'
    },
    inner2: {
        val1: 'c',
        val2: 'd'
    }
};

Of course, you can do that dynamically, rather than all at once:

var outer = {};
outer.inner1 = {};
outer.inner1.val1 = 'a';
...

Then, to do what you're looking for, you'll need to use arrays:

var result = [];
for (var i=0; i<x; ++i) {
  result[result.length] = GetIndividualResult(i);
}

1 Comment

Not exactly what I needed but some building blocks towards a solution. Cheers.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.