skip to Main Content

I would like to copy a object from an existing object.

But I only need the field I want below

Since whiteList approach is required, I couldn’t simply copy the whole object and then use a delete approach to remove unwanted fields

So, my approach right now is like below

const copiedData = {
    productId: data.productId,
    time: data.time,
    lastModifiedDate: data.lastModifiedDate
    plan: {
        planDetails: optionallySelProperties({
            data: data.plan.planDetails,
            dependencies: planDetailsDependencies
        }), //--XXXXXXXXXXXXXXXX problem comes in
         
        analysis: optionallySelProperties({
            data: data.analysis,
            dependencies: ......})
        products: .......,
        }
}

if they properties I need is a object. I would wrap it by a function that support optionally to select properties.

const planDetailsDependencies = [
  'planName',
  'status',
  'customproductAllocations',
]


const optionallySelProperties = ({ data, dependencies }: IOptionallySelProperties) => {
  return Object.entries(pick(data, dependencies) || {}).reduce((ret, cur) => {
    return {
      ...ret,
      [cur[0]]: cur[1],
    }
  }, {})
}

PS: pick is lodash function

rn, if the data that is passed into optionallySelProperties contains nested objects and I also need to optionally select properties. I couldnt achieve this in my function.

Is there a way to achieve this?

Here is the data I wanted to copy

const data = {
    "abcId": "b27e21f7",
    "productId": "G0221837387", //----- field I want to take 
    "time": 1698303900879, //----- field I want to take
    "member": { //----- field I want to take
        "teamMembers": [{
                "roles": [],
                "type": "Team Member",
                "name": "Me",
            }
        ]
    },
    "plan": {  //----- field I want to take
        "id": 86,  //----- field I want to take
        "lastModifiedDate": "2023-10-25T01:37:58.648146", //----- field I want to take
        "planDetails": { //----- field I want to take
            "planName": "20230202",
            "options": [{
                    "value": 1,
                    "text": "Pineapple"
                }
            ],
            "status": "DRAFT", //----- field I want to take
            "customproductAllocations": [{ //----- field I want to take
                    "id": 24744,   
                    "allocationDetail": { //----- field I want to take 
                        "name": "Created on 17 August 2023",
                        "dollar": "USD", //----- field I want to take
                        "allocations": [{
                                "id": "1005",
                                "name": "chocolatePreferred", //----- field I want to take                            
                            }, {
                                "id": "1007",
                                "name": "chocolate Large Cap", //----- field I want to take                           
                            }, 
                        ]
                    }
            ]
        },
        "products": { //----- field I want to take
            "inital": 169000000,   //----- field I want to take
            "externalproducts": [{  //----- field I want to take
                    "id": 659,
                    "name": "Additional", //----- field I want to take
                }
            ],
            "productAllocation": { //----- field I want to take
                "productAllocation": [{
                        "id": "1005",                
                        "category": "Card",  //----- field I want to take     
                    }, {
                        "id": "1007",
                        "category": "Fruit", //----- field I want to take
                    }, 
                ]
            }
        },
        "analysis": {  //----- field I want to take
            "analysisA": { //----- field I want to take
                "id": 50443,
                "key": "Liq", //----- field I want to take
              
            
            },
            "analysisB": { //----- field I want to take
                "id": 50443,
                "key": "dity", //----- field I want to take
            }
        },
    },
}

3

Answers


  1. You could define the shape of the desired result with another object.
    And use that object to build the new one.
    For example

    const data = {
      required1: 'data',
      required2: true,
      notRequired1: 'value',
      required3: 9,
      required4: {
        notRequiredNested1: 3,
        requiredNested1: [
          {
            required1: '___',
            notRequired1: {}
          },
          {
            required1: 'string',
            notRequired1: {}
          }
        ]
      }
    }
    
    const requiredKeys = {
      required1: undefined,
      required2: undefined,
      required3: undefined,
      required4: {
        requiredNested1: [
          {
            required1: undefined
          }
        ]
      },
    }
    
    const clonedData = copyRequiredProperties(data, requiredKeys)
    
    console.log('Data', data)
    console.log('Cloned Data', clonedData)
    
    function copyRequiredProperties(obj, requiredKeys) {
      const clonedObj = {}
    
      for (const [key, value] of Object.entries(requiredKeys)) {
        if (value === undefined) {
          clonedObj[key] = obj[key]
    
          continue
        }
    
        if (Array.isArray(value)) {
          clonedObj[key] = []
    
          if (typeof value[0] === 'object') {
            for (const item of obj[key]) {
              const requiredKeysOfArrayItems = value[0]
              const clonedItem = copyRequiredProperties(item, requiredKeysOfArrayItems)
              clonedObj[key].push(clonedItem)
            }
          }
          else {
            for (const item of obj[key]) {
              clonedObj[key].push(item)
            }
          }
    
          continue
        }
    
        if (typeof value === 'object') {
          const requiredKeysOfNestedObject = value
          clonedObj[key] = copyRequiredProperties(obj[key], requiredKeysOfNestedObject)
    
          continue
        }
      }
    
      return clonedObj
    }
    Login or Signup to reply.
  2. The obvious solution to the OP’s problem is to implement a single function which recursively clones any provided data-structure, but not entirely since there are exceptions.

    Note … Not the other way around … The OP’s request is better described by a ignore-list than a required-list.

    Besides its first parameter, which is the to be cloned value/data, this function also accepts a Set instance which features the key-names of properties which are supposed to not to be cloned (hence a ignore list).

    A first rough solution, though it does not fully cover the OP’s use case of precisely targeting specific key-paths, would work with such a key-ignore list and does look like follows …

    function cloneDataAndIgnoreKeys(
      dataSource, ignoredKeys = new Set, dataTarget = {}
    ) {
      if (Array.isArray(dataSource)) {
    
        dataTarget = dataSource
          .map(item =>
            cloneDataAndIgnoreKeys(item, ignoredKeys)
          );
      } else if (!!dataSource && (typeof dataSource === 'object')) {
    
        dataTarget = Object
          .entries(dataSource)
          .reduce((target, [key, value]) => {
    
            if (!ignoredKeys.has(key)) {
              target[key] =
                cloneDataAndIgnoreKeys(value, ignoredKeys);
            }
            return target;
    
          }, dataTarget);
    
      } else {
        dataTarget = dataSource;
      }
      return dataTarget;
    }
    
    const sampleData = {
      "abcId": "b27e21f7",
      "productId": "G0221837387", //----- field I want to take 
      "time": 1698303900879, //----- field I want to take
      "member": { //----- field I want to take
        "teamMembers": [{
          "roles": [],
          "type": "Team Member",
          "name": "Me",
        }],
      },
      "plan": { //----- field I want to take
        "id": 86, //----- field I want to take
        "lastModifiedDate": "2023-10-25T01:37:58.648146", //----- field I want to take
        "planDetails": { //----- field I want to take
          "planName": "20230202",
          "options": [{
            "value": 1,
            "text": "Pineapple",
          }],
          "status": "DRAFT", //----- field I want to take
          "customproductAllocations": [{ //----- field I want to take
            "id": 24744,
            "allocationDetail": { //----- field I want to take 
              "name": "Created on 17 August 2023",
              "dollar": "USD", //----- field I want to take
              "allocations": [{
                "id": "1005",
                "name": "chocolatePreferred", //----- field I want to take
              }, {
                "id": "1007",
                "name": "chocolate Large Cap", //----- field I want to take
              }],
            }
          }],
          "products": { //----- field I want to take
            "inital": 169000000, //----- field I want to take
            "externalproducts": [{ //----- field I want to take
              "id": 659,
              "name": "Additional", //----- field I want to take
            }],
            "productAllocation": { //----- field I want to take
              "productAllocation": [{
                "id": "1005",
                "category": "Card", //----- field I want to take     
              }, {
                "id": "1007",
                "category": "Fruit", //----- field I want to take
              }],
            },
          },
          "analysis": { //----- field I want to take
            "analysisA": { //----- field I want to take
              "id": 50443,
              "key": "Liq", //----- field I want to take
            },
            "analysisB": { //----- field I want to take
              "id": 50443,
              "key": "dity", //----- field I want to take
            },
          },
        },
      },
    };
    
    console.log(
      cloneDataAndIgnoreKeys(
        sampleData,
        new Set(['abcId', 'id']),
      )
    );
    .as-console-wrapper { min-height: 100%!important; top: 0; }

    The above first approach can be refined, in order to fully cover the OP’s specific use case.

    The Set instance now would feature keypath values instead of the not precise enough property names, whereas the recursive function would aggregate and pass to itself the current scope’s keypath upon which the ignoring decision is going to be made.

    The keypath based approach also allows notations which …

    • either can target any array-item within the path regardless of its exact array index

      'planDetails.customproductAllocations[n]allocationDetail.allocations[n]id'
      
    • or do precisely target a specific member of the data-structure.

      'planDetails.customproductAllocations[0]allocationDetail.allocations[3]id'
      
    function cloneDataAndIgnoreKeypaths(
      dataSource, ignoredKeypaths = new Set, keypath = '', dataTarget = {},
    ) {
      if (Array.isArray(dataSource)) {
    
        dataTarget = dataSource
          .map((item, idx) =>
    
            cloneDataAndIgnoreKeypaths(
              item, ignoredKeypaths, `${ keypath }[${ idx }]`,
            )
          );
      } else if (!!dataSource && (typeof dataSource === 'object')) {
    
        dataTarget = Object
          .entries(dataSource)
          .reduce((target, [key, value]) => {
    
            const currentKeypath = (
    
              // - handling the root-path case.
              (!keypath && key) ||
    
              // - handling concatenation to an array item.
              (keypath.endsWith(']') && (keypath + key)) ||
    
              // - handling concatenation to an object member.
              `${ keypath }.${ key }`
            );
            const generalizedArrayItemPath = currentKeypath
              .replace((/[d+]/g), '[n]')
    
            // look for both matches ...
            if (
              // - the exact match of a keypath,
              !ignoredKeypaths.has(currentKeypath) &&
              // - the match of any array-item within the
              //   path regardless of its exact array index.
              !ignoredKeypaths.has(generalizedArrayItemPath)
            ) {
              target[key] =
    
                cloneDataAndIgnoreKeypaths(
                  value, ignoredKeypaths, currentKeypath,
                )
            }
            return target;
    
          }, dataTarget);
    
      } else {
        dataTarget = dataSource;
      }
      return dataTarget;
    }
    
    const sampleData = {
      "abcId": "b27e21f7",
      "productId": "G0221837387", //----- field I want to take 
      "time": 1698303900879, //----- field I want to take
      "member": { //----- field I want to take
        "teamMembers": [{
          "roles": [],
          "type": "Team Member",
          "name": "Me",
        }],
      },
      "plan": { //----- field I want to take
        "id": 86, //----- field I want to take
        "lastModifiedDate": "2023-10-25T01:37:58.648146", //----- field I want to take
        "planDetails": { //----- field I want to take
          "planName": "20230202",
          "options": [{
            "value": 1,
            "text": "Pineapple",
          }],
          "status": "DRAFT", //----- field I want to take
          "customproductAllocations": [{ //----- field I want to take
            "id": 24744,
            "allocationDetail": { //----- field I want to take 
              "name": "Created on 17 August 2023",
              "dollar": "USD", //----- field I want to take
              "allocations": [{
                "id": "1005",
                "name": "chocolatePreferred", //----- field I want to take
              }, {
                "id": "1007",
                "name": "chocolate Large Cap", //----- field I want to take
              }],
            }
          }],
          "products": { //----- field I want to take
            "inital": 169000000, //----- field I want to take
            "externalproducts": [{ //----- field I want to take
              "id": 659,
              "name": "Additional", //----- field I want to take
            }],
            "productAllocation": { //----- field I want to take
              "productAllocation": [{
                "id": "1005",
                "category": "Card", //----- field I want to take     
              }, {
                "id": "1007",
                "category": "Fruit", //----- field I want to take
              }],
            },
          },
          "analysis": { //----- field I want to take
            "analysisA": { //----- field I want to take
              "id": 50443,
              "key": "Liq", //----- field I want to take
            },
            "analysisB": { //----- field I want to take
              "id": 50443,
              "key": "dity", //----- field I want to take
            },
          },
        },
      },
    };
    
    console.log(
      cloneDataAndIgnoreKeypaths(
        sampleData,
        new Set([
          'abcId',
          'plan.planDetails.customproductAllocations[n]id',
          'plan.planDetails.customproductAllocations[n]allocationDetail.name',
          'plan.planDetails.customproductAllocations[n]allocationDetail.allocations[n]id',
          'plan.planDetails.products.externalproducts[n]id',
          'plan.planDetails.products.productAllocation.productAllocation[n]id',
          'plan.planDetails.analysis.analysisA.id',
          'plan.planDetails.analysis.analysisB.id',
        ]),
      )
    );
    .as-console-wrapper { min-height: 100%!important; top: 0; }
    Login or Signup to reply.
  3. Here’s a simple picker inspired by ArkType syntax
    It’s made to work with arrays, records and optional keys

    https://tsplay.dev/wjpebm has typedefs for autocompletion

    function mapObject(obj, mapper) {
        return Object.fromEntries(Object.entries(obj)
            .map(([k, v]) => mapper(k, v))
            .filter((e) => e?.length === 2));
    }
    function arkPick(obj, schema) {
        if (Array.isArray(obj))
            return obj.map(e => arkPick(e, schema));
        if (Object.keys(schema)[0] === '__record')
            return mapObject(obj, (k, v) => [k, arkPick(v, schema.__record)]);
        return mapObject(schema, (k, v) => {
            let opt = k.endsWith('?');
            if (opt)
                k = k.slice(0, -1);
            if (!(k in obj)) {
                if (opt)
                    return [];
                else
                    throw new Error(`missign property ${k}`);
            }
            if (v === 'any' || v === true)
                return [k, obj[k]];
            if (typeof v === 'string') {
                if (typeof obj[k] === v)
                    return [k, obj[k]];
                else
                    throw new Error(`incorrect type of property ${k}`);
            }
            return [k, arkPick(obj[k], v)];
        });
    }
    
    console.log(arkPick(data, {
      productId: 'string',
      time: 'number',
      'member?': 'object', // optional
      'ZZZmissingZZZ?': 'any', // ignored
      plan: {
        id: 'number',
        lastModifiedDate: 'string',
        planDetails: {
          customproductAllocations: {
            allocationDetail: { // array
              name: 'string'
            }
          }
        },
        analysis: {
          __record: { // record
            key: 'string'
          }
        }
      }
    }))
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search