Build an array of nested objects from arrays of properties names and values

Viewed 99

I´m building an integration package for a legacy system where I receive all data, including the data structure definition (metadata) using lists.

Fields (or properties) in metadata list changes depeding on what type of record is being retrieved. The example below shows a customer record, but it may be a product record, or a stock item record - then I would receive different metadata.

I need to dynamically build the object structure from the metadata and then fetch its data from the given lists.

metadata list for a customer call to legacy system:

[
    {
        field: "customer.id",
        type: "string"
    },
    {
        field: "customer.name",
        type: "string"
    },
    {
        field: "customer.credit.rate",
        type: "string"
    },
    {
        field: "customer.credit.card.limit",
        type: "number"
    },
    {
        field: "customer.credit.card.due",
        type: "number"
    },
    {
        field: "customer.credit.card.paymentDay",
        type: "number"
    },
    {
        field: "customer.address.street",
        type: "string"
    },
    {   
        field: "customer.address.number",
        type: "string"
    },
    {
        field: "customer.address.zip",
        type: "string"
    }
]

metadata list for a product call to legacy system:

[
    {
        field: "product.id",
        type: "string"
    },
    {
        field: "product.name",
        type: "string"
    },
    {
        field: "product.sale.customer.id",
        type: "string"
    },
    {
        field: "product.sale.customer.name",
        type: "number"
    },
    {
        field: "product.sale.price",
        type: "number"
    }
]

products list:

[
    { id: "1", product_id: "1", name: "Spounge" },
    { id: "2", product_id: "2", name: "CD Box" },
]

customers list:

[
    { id: "1", name: "John"},
    { id: "2", name: "Mary"}
]

credits list:

[
    { id: "1", customer_id: "1", rate: "A" },
    { id: "2", customer_id: "2", rate: "B" },
]

cards list:

[
    { id: "1", credit_id: "1", limit: 10000, due: 0, paymentDay: 15 },
    { id: "2", credit_id: "2", limit: 1000, due: 1000, paymentDay: 2 },
]

address list:

[
    { id: "1", customer_id: "1", street: "46th St", number: "33", zip: "12312" },
    { id: "2", customer_id: "2", street: "55th Avenue", number: "349", zip: "39458" },
]

products list:

[
    { id: "1", product_id: "1", name: "Spounge" },
    { id: "2", product_id: "2", name: "CD Box" },
]

customers list:

[
    { id: "1", name: "John"},
    { id: "2", name: "Mary"}
]

credits list:

[
    { id: "1", customer_id: "1", rate: "A" },
    { id: "2", customer_id: "2", rate: "B" },
]

cards list:

[
    { id: "1", credit_id: "1", limit: 10000, due: 0, paymentDay: 15 },
    { id: "2", credit_id: "2", limit: 1000, due: 1000, paymentDay: 2 },
]

sales list:

[
    { id: "1", customer_id: "1", product_id: "1", price: 123.44 },
    { id: "2", customer_id: "2", product_id: "2", price: 3.22 },
]

My goal is to have that data in a single javascript array of objects:

Example for the customer call to legacy system:

customers = [
    {
        id: 1,
        name: "John",
        credit {
            rate: "A",
            card {
                limit: 10000,
                due: 0,
                paymentDay: 15
            }
        },
        address {
            street: "46th St",
            number: "33",
            zip: "12312"
        }
    },
    {
        id: 2,
        name: "Mary",
        credit {
            rate: "B",
            card {
                limit: 1000,
                due: 1000,
                paymentDay: 2
            }
        },
        address {
            street: "55th Avenue",
            number: "349",
            zip: "39458"
        }
    }
]

Example for the product call to legacy system:

customers = [
    {
        id: 1,
        name: "Spounge",
        sale {
            price: 123.44,
            customer {
                id: "1",
                name: "John"
            }
        }
    },
    {
        id: 2,
        name: "CD Box",
        sale {
            price: 3.22,
            customer {
                id: "2",
                name: "Mary"
            }
        }
    }
]

I´m really very confused finding the right way of doing this. What would be the best approach to start from? Start from building a nested object and filling it up? Any suggested algorithm?

NOTE: The legacy system changes the data based on the type of object is being read and data availability. So, metadata list and the lists are not fixed. For every API call into the legacy system I receive a different metadata array and different lists, depending on what data is available and the query is being made, so I need a generic algorithm to build the object based on the given data.

2 Answers

This approach uses a data base with the given relations between the named arrays (aka tables) and creates an object with the relations for a faster access.

The relations, like n : m are collected but only the first (or only) 1 : 1 relation is used.

const
    getDatabase = data => Object
        .entries(data)
        .reduce((r, [table, rows]) => {
            r[table] = r[table] || {};
            rows.forEach(row => {
                r[table][row.id] = row;
                Object.entries(row).forEach(([key, id]) => {
                    const
                        getObject = (object, keys) => keys.reduce((o, k, i, { length }) => o[k] = o[k] || (i + 1 === length ? [] : {}), object),
                        [reference, suffix] = key.split('_');

                    if (!suffix) return;

                    getObject(r.relations, [table, row.id, reference]).push(id);
                    getObject(r.relations, [reference, id, table]).push(row.id);
                });
            });
            return r;
        }, { relations: {} }),
    getDatasets = (db, table, metadata) => Object
        .values(db[table])
        .map(data => {
            const references = [[table, data.id]];

            return metadata.reduce((target, { field }) => {
                let source = data,
                    path = field.split('.'),
                    last = path.pop();

                path
                    .reduce((t, k) => {
                        if (k in db) {
                            let i = references.length;
                            while (i--) {
                                const
                                    [table, id] = references[i],
                                    temp = db.relations[table]?.[id]?.[k];

                                if (!temp) continue;
                                source = db[k][temp[0]];
                                if (!references.some(a => a[0] === k && a[1] === source.id)) {
                                    references.push([k, source.id]);
                                }
                                break;
                            }
                        }
                        return t[k] = t[k] || {};
                    }, target)
                    [last] = source[last];

                return target;
            }, {});
        }),
    customer = [{ id: "customer1", name: "John" }, { id: "customer2", name: "Mary" }],
    credit = [{ id: "credit1000", customer_id: "customer1", rate: "A" }, { id: "credit2000", customer_id: "customer2", rate: "B" }],
    card = [{ id: "card1", credit_id: "credit1000", limit: 10000, due: 0, paymentDay: 15 }, { id: "card2", credit_id: "credit2000", limit: 1000, due: 1000, paymentDay: 2 }],
    address = [{ id: "address1", customer_id: "customer1", street: "46th St", number: "33", zip: "12312" }, { id: "address2", customer_id: "customer2", street: "55th Avenue", number: "349", zip: "39458" }],
    product = [{ id: "product1", name: "Spounge" }, { id: "product2", name: "CD Box" }],
    sale = [{ id: "sale1", customer_id: "customer1", product_id: "product1", price: 123.44 }, { id: "sale2", customer_id: "customer2", product_id: "product2", price: 3.22 }],
    db = getDatabase({ customer, credit, card, address, product, sale }),
    metadata1 = [{ field: "customer.id", type: "string" }, { field: "customer.name", type: "string" }, { field: "customer.credit.rate", type: "string" }, { field: "customer.credit.card.limit", type: "number" }, { field: "customer.credit.card.due", type: "number" }, { field: "customer.credit.card.paymentDay", type: "number" }, { field: "customer.address.street", type: "string" }, { field: "customer.address.number", type: "string" }, { field: "customer.address.zip", type: "string" }],
    metadata2 = [{ field: "product.id", type: "string" }, { field: "product.name", type: "string" }, { field: "product.sale.customer.id", type: "string" }, { field: "product.sale.customer.name", type: "number" }, { field: "product.sale.price", type: "number" }],
    customers = getDatasets(db, 'customer', metadata1),
    products = getDatasets(db, 'product', metadata2);

console.log(customers);
console.log(products);
.as-console-wrapper { max-height: 100% !important; top: 0; }

You could first iterate through each customer and then each metadata entry which you will use to build nested structure. On each new field you can check if the current key exists in levels and if it does you get the correct value for it.

const metadata = [{"field":"customer.id","type":"string"},{"field":"customer.name","type":"string"},{"field":"customer.credit.rate","type":"string"},{"field":"customer.credit.card.limit","type":"number"},{"field":"customer.credit.card.due","type":"number"},{"field":"customer.credit.card.paymentDay","type":"number"},{"field":"customer.address.street","type":"string"},{"field":"customer.address.number","type":"string"},{"field":"customer.address.zip","type":"string"}]
const customers = [{"id":"1","name":"John"},{"id":"2","name":"Mary"}]
const credits = [{"id":"1","customer_id":"1","rate":"A"},{"id":"2","customer_id":"2","rate":"B"}]
const cards = [{"id":"1","credit_id":"1","limit":10000,"due":0,"paymentDay":15},{"id":"2","credit_id":"2","limit":1000,"due":1000,"paymentDay":2}]
const address = [{"id":"1","customer_id":"1","street":"46th St","number":"33","zip":"12312"},{"id":"2","customer_id":"2","street":"55th Avenue","number":"349","zip":"39458"}]

const result = []
const levels = {
  customer: customers,
  credit: credits,
  card: cards,
  address: address
}

const rootKey = metadata[0].field.split('.')[0]
const getLevel = (data, key, pKey, id) => {
  return data[key].find(e => {
    return e[`${pKey}_id`] == id
  })
}

levels[rootKey].forEach(object => {
  let customer = {}

  metadata.forEach(({ field, type }) => {
    let pId = object.id;
    let pKey = rootKey;
    let level = object;

    field.split('.').reduce((r, key, i, arr) => {
      if (i > 0 && key in levels) {
        level = getLevel(levels, key, pKey, pId) || {}

        if (level) {
          pKey = key;
          pId = level.id
        }
      }

      if (!arr[i + 1]) {
        r[key] = level[key]
        return r[key]
      }

      return r[key] = r[key] || {}
    }, customer)
  })

  result.push(customer.customer);
})

console.log(result)

Related