0

I have an object that look like this:

        {
            "id": 45745049
            "seller": {
                "first_name": "Sam",
                "last_name": "Smith",
                "email": "samsmith@smith.com",
                "phone": {
                    "number": "1111-1111",
                    "verified": false
                },
            },
            "order_items": [
                {
                    "item": {
                        "id": "29239765",
                        "title": "item1",
                        "colors": [
                             "red",
                             "green",
                             "blue"
                        ]
                    },
                    "quantity": 1,
                    "unit_price": 230,
                },
                {
                    "item": {
                        "id": "238457363",
                        "title": "item2",
                        "colors": [
                             "red"
                        ]
                    },
                    "quantity": 2,
                    "unit_price": 110,
                }
            ],
            "date_created": "2020-08-03T12:17:25.000-04:00",
            "date_last_updated": "2020-08-03T16:51:35.61Z"
        }

I want an array with pairs of EVERY key in the object with the value.

For example:

[
  ["id", 45745049], 
  ["first_name", "Sam"], 
  ....., 
  ["phone.number", "1111-1111"], 
  ["phone.verified", false], 
  ....etc
]

Everything Ok until that point. The problem is when a property is an array of objects. The output I want is the following:

[
   ..., 
   ["order_items1.item.id", 29239765], 
   ["order_items1.item.colors1", "red"], 
   ["order_items1.item.colors2", "green"], 
   ..., 
   ["order_items2.item.id", 238457363], 
   ["order_items2.item.colors1", "red"], 
   ...etc
]

So it needs to check if the property is an array and add the position number if so.

I know I need a recursive function but I dont know how to do it. This is what I got until now.

getObjectKeys = (obj) => {
    let FieldName = "";
    let FieldValue = "";
    for(var prop in obj) {
        FieldName += prop;
        if(!(prop instanceof Array) && (typeof prop !== "object") && obj[prop]) {
            FieldValue = obj[prop];
        } else if(prop instanceof Array && prop.length !== 0){
            prop.forEach((innerItem, i) => {
                FieldName += `${i+1}.`;
                // check if the inner item is an array or whatever an do it all again
                // Dont know what to do here.                   
            });
        } else {
            getObjectKeys(obj[prop]);
        }        
    }
    return [FieldName, FieldValue];
    
}

Note: I dont want the empty or null keys.

I would be very grateful if someone can help me. Thanks anyways!

Tomi Melone
  • 145
  • 1
  • 1
  • 7
  • Are you sure you want `["order_items1.item.id", 29239765]`? This not a proper array because of the dot inside the key . If you e.g. make a console.log from this array this key/value pair is not presented. You can only get the value by address it by address it through `arr["order_items1.item.id"]`. – Sascha Aug 25 '20 at 20:34
  • @Sascha Yeah, I know. But I dont want to access that key later. Only need the key as a string – Tomi Melone Aug 25 '20 at 20:39

1 Answers1

1

This does something very similar to what you're looking for. It's a technique I use often.

const getPaths = (obj) =>
  Object (obj) === obj
    ? Object .entries (obj) .flatMap (([k, v]) => getPaths (v) .map (p => [k, ... p]))
    : [[]]

const path = (ps) => (obj) => 
  ps .reduce ((o, p) => (o || {}) [p], obj)

const flatten = (obj) => 
  Object .fromEntries (getPaths (obj) .map (p => [p.join('.'), path (p) (obj)]))

const input = {id: 45745049, seller: {first_name: "Sam", last_name: "Smith", email: "samsmith@smith.com", phone: {number: "1111-1111", verified: false}}, order_items: [{item: {id: "29239765", title: "item1", colors: ["red", "green", "blue"]}, quantity: 1, unit_price: 230}, {item: {id: "238457363", title: "item2", colors: ["red"]}, quantity: 2, unit_price: 110}], date_created: "2020-08-03T12: 17: 25.000-04: 00", date_last_updated: "2020-08-03T16: 51: 35.61Z"}

console .log (flatten (input))
.as-console-wrapper {min-height: 100% !important; top: 0}

The differences are that there is a separator before the array index and that I use zero-based arrays, not one-based arrays.

I would suggest that it's a much better output format. If nothing else, it would probably allow you to rehydrate the original format. But if you want to change it, you should probably simply reduce the path to combine the numeric elements with their predecessors, something like:

const flatten = (obj) => 
  Object .fromEntries (getPaths (obj) .map (p => [
    p .reduce (
      (a, k) => /^\d+$/ .test(k) ? [...a .slice (0, -1), a [a .length - 1] + (1 + (+k))] : [...a, k], 
      []
    ) .join ('.'), 
    path2 (p) (obj)
  ]))

But this would require changes if the outer object might be an array.

Again, though, absent a very good reason to use your requested format, I would strongly recommend my alternative.

Scott Sauyet
  • 49,207
  • 4
  • 49
  • 103
  • Thank you so much!! I dont understand the code completely and maybe I will need to make some tweaks but thats what I was looking for (kind of). I'm going to read it more carefully later because my head is going to explode now lol. Thank you again, you're a genius. – Tomi Melone Aug 25 '20 at 21:59