Skip to content

Using Custom Operators

DonMartin76 edited this page Jul 22, 2015 · 3 revisions

In many cases, it is convenient to make use of custom operators (see also Config File Documentation) in order to...

  • Increase the readability of the transformation
  • Re-use functionality in multiple fields
  • Re-use functionality in multiple transformations (using includes)

In principle, anything which can be done using custom operators can be accomplished without custom operators, albeit it most of the time makes things more clear.

Increasing readability in transformations

In some cases, it is necessary to derive the content of a field from one or more other source fields, and/or using lookup maps. In other cases, a simple mapping has to be done. This section will show how this can be accomplished, (a) directly using field definitions, and (b) using custom operators to increase the readability of the transformation.

Deriving fields

In this case, we want to accomplish the following thing: We have two different lookup maps which contain two different set of data. Given an ID, we want to find out whether the ID is present in the first set, in the second set, or not at all. The lookup maps are named SetA and SetB, and as a result in a field Location we want as an output A (for SetA), B (for SetB), AB (for both sets) or none.

Using a pure field definition, we would end up with a field mapping like this:

<Fields>
  ...
  <Field name="Location">If(And(HasKey("SetA", $ID), HasKey("SetB", $ID)), "AB", If(HasKey("SetA", "A", If(HasKey("SetB", $ID), "B", "none")))</Field>
  ...
</Fields>

This will work alright, but unfortunately, nobody will be able to read this at first sight and understand what is going on. It takes a lot of unwinding parenthesises and seeing which parameter goes where to really get the idea of what the expression actually does.

Enter custom operators. Using a custom operator's Switch tag, we can write this function in a way which is much easier to read:

<CustomOperators>
  <CustomOperator name="GetLocation" paramCount="1" returnType="string">
    <Parameters>
      <Parameter name="id" type="string" />
    </Parameters>
    <Switch>
      <Case condition='And(HasKey("SetA", %id), HasKey("SetB", %id))'>"AB"</Case>
      <Case condition='HasKey("SetA", %id)'>"A"</Case>
      <Case condition='HasKey("SetB", %id)'>"B"</Case>
      <Otherwise>"none"</Otherwise>
    </Switch>
  </CustomOperator>
</CustomOperators>

<Fields>
  ...
  <Field name="Location">GetLocation($ID)</Field>
  ...
</Fields>

Admitted, the custom operator notation is a lot bulkier, but in return much clearer. It is written as a switch/case with clear conditions and clear return calues. Additionally, we could re-use it for other fields, if necessary:

<Field name="SecondLocation">GetLocation($ID2)</Field>

Re-using functionality using includes

Another thing which is useful with custom operators is that they may be put into "include" transformation files (see the include definition). This enables building "modules" with specific functionality.

An example for this may be the mapping to Salesforce User IDs (the "Owner" of records in Salesforce) based on a map of Legacy IDs and Salesforce User IDs. Let the list of Salesforce users and the corresponding Legacy User IDs be stored in a CSV file with the following format:

LegacyId;SalesforceId
0099128910;1008af09090112a008AAG
0099128112;1008af09090112a009AAG
...
XXXXXXXXXX;1008af09090112a1faAAG

All legacy IDs are stored in the first column, and the corresponding Salesforce IDs are stored in the second column. The last value (here in this example) is a special value XXXXXXXXXX which denominates the default Salesforce User, for example a technical migration user which is to be used as the Owner of records for which an owner cannot be found.

Finding the owner id for records in Salesforce is a task which is recurring for many types of entities, e.g. Account, Contact and others, depending on which data has to be imported into Salesforce. This means there will be many transformation files needing similar functionality: Mapping legacy user IDs to Salesforce user IDs.

Enter include files, lookup maps and custom operators: We want to implement a custom operator GetOwner which maps from a legacy user id to a Salesforce ID, and, if not found, returns the user ID of the migration user.

A possible include transformation XML getowner.xml might look like this:

<?xml version="1.0" encoding="utf-8"?>
<Transformation>
  <LookupMaps>
    <LookupMap name="LegacyToSFId" keyField="LegacyId">
      <Source config="delim=';'">file://..\output\users\users.csv</Source>
    </LookupMap>
  </LookupMaps>

  <CustomOperators>
    <CustomOperator name="GetOwner" paramCount="1" returnType="string">
      <Parameters>
        <Parameter name="legacyId" type="string" />
      </Parameters>
      <Switch>
        <Case condition='HasKey("LegacyToSFId", %legacyId)'>LegacyToSFId(%legacyId, $SalesforceId)</Case>
        <Otherwise>LegacyToSFId("XXXXXXXXX", $SalesforceId)</Otherwise>
      </Switch>
    </CustomOperator>
  </CustomOperators>
</Transformation>

Using this custom operator in a transformation is now quite easy, as we both have included the custom operator GetOwner and the lookup map LegacyToSFId implicitly. You only have to include getowner.xml and use the operator:

<Transformation>
  <Includes>
    <Include>getowner.xml</Include>
  </Includes>
  
  ...

  <Fields>
    ...
    <Field name="OwnerId">GetOwner($legacyId)</Field>
    ...
  </Fields>
</Transformation>

We have now hidden the complexity of the finding of the Salesforce ID of the Owner into the include file, and have thus uncluttered the transformation in order to emphasis what it actually does.

Clone this wiki locally