opsflowhq / method.js

Framework for developing business systems fast
0 stars 0 forks source link

Declarative object mapping #6

Open VladymyrPylypchatin opened 1 day ago

VladymyrPylypchatin commented 1 day ago

It's hard to define and maintain mapping in integrations.

It would great to have a way to define mappings in a delcaritive way like in example bellow:


// Define the mapping rules outside the function
const mapping = {
  "ariveLoanId": { path: "ariveLoanId" },
  "sysGUID": { path: "sysGUID" },
  "leadSource": { path: "leadSource", default: "" },
  "loanCreatedFrom": { path: "loanCreatedFrom", default: "" },
  "industryChannel": { path: "industryChannel", default: "" },
  "createDateTime": { path: "createDateTime", default: "" },
  "modifiedDateTime": { path: "modifiedDateTime", default: "" },
  "APPLICATION_INTAKE": {
    path: "loanStatusHistory",
    action: data => {
      const entry = data.find(s => s.status === "APPLICATION_INTAKE");
      return entry ? entry.date : "";
    }
  },
  "currentLoanStatus_status": { path: "currentLoanStatus.status", default: "" },
  "currentLoanStatus_date": { path: "currentLoanStatus.date", default: "" },
  "currentLoanStatus_adverseReason": { path: "currentLoanStatus.adverseReason", default: "" },

  // Example for nested arrays
  "HOI_status": {
    path: "loanTrackers",
    action: data => {
      const tracker = data.find(t => t.name === "HOI");
      return tracker ? tracker.currentTrackerStatus.name : "NOT_ORDERED";
    }
  },
  "HOI_date": {
    path: "loanTrackers",
    action: data => {
      const tracker = data.find(t => t.name === "HOI");
      return tracker ? tracker.currentTrackerStatus.Date : "";
    }
  },

  // Employment serialized to JSON
  "employment": {
    path: "employment",
    action: data => JSON.stringify(data.map(emp => ({
      employerName: emp.employerName,
      positionDesc: emp.positionDesc,
      monthlyIncome: emp.monthlyIncome,
      employerPhone: emp.employerPhone,
      classificationType: emp.classificationType,
      startDate: emp.startDate,
      endDate: emp.endDate
    })))
  },

  // Nested borrower data
  "loanBorrower1_firstName": {
    path: "loanBorrowers",
    action: data => {
      const borrower = data.find(b => b.applicantType === "Borrower");
      return borrower ? borrower.firstName : "";
    }
  },

  // Loan Team with dynamic indices
  "loanTeamUser1_emailAddressText": {
    path: "loanTeam",
    action: data => data[0]?.emailAddressText || ""
  },
  "loanTeamUser1_firstName": {
    path: "loanTeam",
    action: data => data[0]?.firstName || ""
  },
  // Additional team members can follow the same pattern...
};

// Map function that uses source and mapping
function mapData(source, mapping) {
  const target = {};

  for (const [key, { path, default: defaultValue, action }] of Object.entries(mapping)) {
    const value = getNestedValue(source, path);

    // If an action is specified in the mapping, apply it
    target[key] = action ? action(value) : (value !== undefined ? value : defaultValue);
  }

  return target;
}

// Helper function to retrieve nested values from the source
function getNestedValue(obj, path) {
  return path.split('.').reduce((acc, key) => acc && acc[key], obj);
}

// Usage example
const sourceData = { /* your source object */ };
const mappedData = mapData(sourceData, mapping);
console.log(mappedData);
VladymyrPylypchatin commented 1 day ago

It would be helpful to have:

Ideally both schema defined as consts and their types is inferred from the value so you don't need to define interfaces.

And then the mapping itself happens by using keys from the schemas.

And then have a helper function that lets to build objects along the lines of: AriveWebhookEvent.build(sourceObject, maping)