This is an automated email from the ASF dual-hosted git repository.

hansva pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/hop.git


The following commit(s) were added to refs/heads/main by this push:
     new cd007456ad Fix Standardize Phone Number country handling, parse 
failures, and variable field names (#8238)
cd007456ad is described below

commit cd007456adb7ed1f5ed89d259f5c2080b51c457a
Author: Lance <[email protected]>
AuthorDate: Thu Sep 3 21:53:42 2026 +0800

    Fix Standardize Phone Number country handling, parse failures, and variable 
field names (#8238)
    
    Signed-off-by: lance <[email protected]>
---
 .../transforms/standardizephonenumber.adoc         |  14 +-
 .../0099-standardize-phone-number-edge-cases.hpl   | 182 +++++++++
 .../datasets/golden-standardize-phone-number.csv   |   8 +
 .../main-0099-standardize-phone-number.hwf         |  23 +-
 .../dataset/golden-standardize-phone-number.json   |  40 ++
 ...9-standardize-phone-number-edge-cases UNIT.json |  48 +++
 .../StandardizePhoneNumber.java                    | 174 +++++---
 .../StandardizePhoneNumberMeta.java                |  67 +--
 .../messages/messages_en_US.properties             |  11 +-
 .../StandardizePhoneFieldTest.java                 | 107 +++++
 .../StandardizePhoneNumberMetaTest.java            | 329 +++++++++++++--
 .../StandardizePhoneNumberTest.java                | 448 +++++++++++++++++++++
 .../standardize-phone-number-transform.xml         |  31 ++
 13 files changed, 1352 insertions(+), 130 deletions(-)

diff --git 
a/docs/hop-user-manual/modules/ROOT/pages/pipeline/transforms/standardizephonenumber.adoc
 
b/docs/hop-user-manual/modules/ROOT/pages/pipeline/transforms/standardizephonenumber.adoc
index f07f15f0d9..960a360ae6 100644
--- 
a/docs/hop-user-manual/modules/ROOT/pages/pipeline/transforms/standardizephonenumber.adoc
+++ 
b/docs/hop-user-manual/modules/ROOT/pages/pipeline/transforms/standardizephonenumber.adoc
@@ -50,15 +50,19 @@ The transform uses 
https://github.com/google/libphonenumber[Google libphonenumbe
 |Transform name|Name of the transform.
 |Input field|The field holding the phone numbers from the input data.
 |Output field|Leave this field empty if you want to update the input field in 
stream. Otherwise a new field (string) will be added to the output data.
-|Country field|The field holding the country code from the input data. The 
country code is required to be in the ISO alpha-2.
-|DefaultCountry|The default country code to use if input data from country 
field is empty or invalid
+|Country field|The field holding the country code from the input data. The 
country code is required to be in the ISO alpha-2. Leading and trailing spaces 
are ignored, and matching is case-insensitive. Leave this empty to always use 
the default country.
+|DefaultCountry|The default country code to use if the country field is empty, 
missing, or contains an unsupported value.
 |Format|The format to be used to standardize the phone numbers. The available 
options are:
 * E164 (default)
 * INTERNATIONAL
 * NATIONAL
 * RFC3966
-|Number type|The result field indicates the type of a processed phone number, 
such as fixed line, mobile...
-|Is valid|The result field indicates whether a phone number is supposed to be 
valid
+|Number type|The result field indicates the type of a processed phone number, 
such as fixed line, mobile... Empty or unparseable numbers are set to `ERROR`.
+|Is valid|The result field indicates whether a phone number is supposed to be 
valid. Empty or unparseable numbers are `false`.
 |===
 
-TIP: If the original phone number already has an international area code, this 
transform will not normalize it to another country even if the international 
area code is different from the country input’s international area code.
\ No newline at end of file
+TIP: If the original phone number already has an international area code, this 
transform will not normalize it to another country even if the international 
area code is different from the country input’s international area code.
+
+TIP: Output, number type, and is-valid field names may contain variables (for 
example `${OUTPUT_FIELD}`).
+
+TIP: If a number cannot be parsed, the original value is written to the output 
field, the number type is `ERROR`, and is-valid is `false`. Numbers that parse 
but are not valid (for example a too-short national number) are still 
formatted; use the is-valid field to filter them.
\ No newline at end of file
diff --git 
a/integration-tests/transforms/0099-standardize-phone-number-edge-cases.hpl 
b/integration-tests/transforms/0099-standardize-phone-number-edge-cases.hpl
new file mode 100644
index 0000000000..ee7830ff69
--- /dev/null
+++ b/integration-tests/transforms/0099-standardize-phone-number-edge-cases.hpl
@@ -0,0 +1,182 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements.  See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+-->
+<pipeline>
+  <info>
+    <name>0099-standardize-phone-number-edge-cases</name>
+    <name_sync_with_filename>Y</name_sync_with_filename>
+    <description>Edge cases: optional/default country, parse failure, country 
trim, output field variables</description>
+    <extended_description/>
+    <pipeline_version/>
+    <pipeline_type>Normal</pipeline_type>
+    <parameters>
+      <parameter>
+        <name>OUTPUT_FIELD</name>
+        <default_value>E164_OUT</default_value>
+        <description>Resolved output field name</description>
+      </parameter>
+    </parameters>
+    <capture_transform_performance>N</capture_transform_performance>
+    
<transform_performance_capturing_delay>1000</transform_performance_capturing_delay>
+    
<transform_performance_capturing_size_limit>100</transform_performance_capturing_size_limit>
+    <created_user>-</created_user>
+    <created_date>2026/09/02 00:00:00.000</created_date>
+    <modified_user>-</modified_user>
+    <modified_date>2026/09/02 00:00:00.000</modified_date>
+  </info>
+  <notepads>
+  </notepads>
+  <order>
+    <hop>
+      <from>Data grid</from>
+      <to>Standardize phone number</to>
+      <enabled>Y</enabled>
+    </hop>
+    <hop>
+      <from>Standardize phone number</from>
+      <to>Preview</to>
+      <enabled>Y</enabled>
+    </hop>
+  </order>
+  <transform>
+    <name>Data grid</name>
+    <type>DataGrid</type>
+    <description/>
+    <distribute>Y</distribute>
+    <custom_distribution/>
+    <copies>1</copies>
+    <partitioning>
+      <method>none</method>
+      <schema_name/>
+    </partitioning>
+    <fields>
+      <field>
+        <set_empty_string>N</set_empty_string>
+        <length>-1</length>
+        <name>CASE</name>
+        <precision>-1</precision>
+        <type>String</type>
+      </field>
+      <field>
+        <set_empty_string>N</set_empty_string>
+        <length>-1</length>
+        <name>PHONE</name>
+        <precision>-1</precision>
+        <type>String</type>
+      </field>
+      <field>
+        <set_empty_string>N</set_empty_string>
+        <length>-1</length>
+        <name>COUNTRY</name>
+        <precision>-1</precision>
+        <type>String</type>
+      </field>
+    </fields>
+    <data>
+      <line>
+        <item>valid_be</item>
+        <item>0499500158</item>
+        <item>BE</item>
+      </line>
+      <line>
+        <item>invalid</item>
+        <item>error</item>
+        <item>BE</item>
+      </line>
+      <line>
+        <item>empty</item>
+        <item/>
+        <item>BE</item>
+      </line>
+      <line>
+        <item>be_space</item>
+        <item>0499500158</item>
+        <item>BE&#32;</item>
+      </line>
+      <line>
+        <item>be_lower</item>
+        <item>0499500158</item>
+        <item>be</item>
+      </line>
+      <line>
+        <item>no_country</item>
+        <item>0499500158</item>
+        <item/>
+      </line>
+      <line>
+        <item>junk</item>
+        <item>999</item>
+        <item>FR</item>
+      </line>
+    </data>
+    <attributes/>
+    <GUI>
+      <xloc>64</xloc>
+      <yloc>96</yloc>
+    </GUI>
+  </transform>
+  <transform>
+    <name>Standardize phone number</name>
+    <type>StandardizePhoneNumber</type>
+    <description/>
+    <distribute>Y</distribute>
+    <custom_distribution/>
+    <copies>1</copies>
+    <partitioning>
+      <method>none</method>
+      <schema_name/>
+    </partitioning>
+    <fields>
+      <field>
+        <input>PHONE</input>
+        <output>${OUTPUT_FIELD}</output>
+        <format>E164</format>
+        <country>COUNTRY</country>
+        <defaultCountry>BE</defaultCountry>
+        <numbertype>TYPE</numbertype>
+        <isvalidnumber>VALID</isvalidnumber>
+      </field>
+    </fields>
+    <attributes/>
+    <GUI>
+      <xloc>304</xloc>
+      <yloc>96</yloc>
+    </GUI>
+  </transform>
+  <transform>
+    <name>Preview</name>
+    <type>Dummy</type>
+    <description/>
+    <distribute>Y</distribute>
+    <custom_distribution/>
+    <copies>1</copies>
+    <partitioning>
+      <method>none</method>
+      <schema_name/>
+    </partitioning>
+    <attributes/>
+    <GUI>
+      <xloc>560</xloc>
+      <yloc>96</yloc>
+    </GUI>
+  </transform>
+  <transform_error_handling>
+  </transform_error_handling>
+  <attributes/>
+</pipeline>
diff --git 
a/integration-tests/transforms/datasets/golden-standardize-phone-number.csv 
b/integration-tests/transforms/datasets/golden-standardize-phone-number.csv
new file mode 100644
index 0000000000..a37918ac3f
--- /dev/null
+++ b/integration-tests/transforms/datasets/golden-standardize-phone-number.csv
@@ -0,0 +1,8 @@
+CASE,E164_OUT,TYPE,VALID
+valid_be,+32499500158,MOBILE,Y
+invalid,error,ERROR,N
+empty,,ERROR,N
+be_space,+32499500158,MOBILE,Y
+be_lower,+32499500158,MOBILE,Y
+no_country,+32499500158,MOBILE,Y
+junk,+33999,UNKNOWN,N
diff --git 
a/integration-tests/transforms/main-0099-standardize-phone-number.hwf 
b/integration-tests/transforms/main-0099-standardize-phone-number.hwf
index 55c901ca0f..7c6169060c 100644
--- a/integration-tests/transforms/main-0099-standardize-phone-number.hwf
+++ b/integration-tests/transforms/main-0099-standardize-phone-number.hwf
@@ -18,7 +18,7 @@ limitations under the License.
 
 -->
 <workflow>
-  <name>main-0090-sort-rows</name>
+  <name>main-0099-standardize-phone-number</name>
   <name_sync_with_filename>Y</name_sync_with_filename>
   <created_user>-</created_user>
   <modified_user>-</modified_user>
@@ -72,6 +72,20 @@ limitations under the License.
       <parallel>N</parallel>
       <attributes_hac/>
     </action>
+    <action>
+      <test_names>
+        <test_name>
+          <name>0099-standardize-phone-number-edge-cases UNIT</name>
+        </test_name>
+      </test_names>
+      <name>0099-standardize-phone-number-edge-cases UNIT</name>
+      <type>RunPipelineTests</type>
+      <attributes/>
+      <xloc>624</xloc>
+      <yloc>64</yloc>
+      <parallel>N</parallel>
+      <attributes_hac/>
+    </action>
   </actions>
   <hops>
     <hop>
@@ -81,6 +95,13 @@ limitations under the License.
       <unconditional>Y</unconditional>
       <enabled>Y</enabled>
     </hop>
+    <hop>
+      <from>0099-standardize-phone-number.hpl</from>
+      <to>0099-standardize-phone-number-edge-cases UNIT</to>
+      <evaluation>Y</evaluation>
+      <unconditional>N</unconditional>
+      <enabled>Y</enabled>
+    </hop>
   </hops>
   <notepads/>
   <attributes/>
diff --git 
a/integration-tests/transforms/metadata/dataset/golden-standardize-phone-number.json
 
b/integration-tests/transforms/metadata/dataset/golden-standardize-phone-number.json
new file mode 100644
index 0000000000..4c3b9621c8
--- /dev/null
+++ 
b/integration-tests/transforms/metadata/dataset/golden-standardize-phone-number.json
@@ -0,0 +1,40 @@
+{
+  "base_filename": "golden-standardize-phone-number.csv",
+  "name": "golden-standardize-phone-number",
+  "description": "Expected output for 
0099-standardize-phone-number-edge-cases",
+  "dataset_fields": [
+    {
+      "field_comment": "",
+      "field_length": -1,
+      "field_type": 2,
+      "field_precision": -1,
+      "field_format": "",
+      "field_name": "CASE"
+    },
+    {
+      "field_comment": "",
+      "field_length": -1,
+      "field_type": 2,
+      "field_precision": -1,
+      "field_format": "",
+      "field_name": "E164_OUT"
+    },
+    {
+      "field_comment": "",
+      "field_length": -1,
+      "field_type": 2,
+      "field_precision": -1,
+      "field_format": "",
+      "field_name": "TYPE"
+    },
+    {
+      "field_comment": "",
+      "field_length": -1,
+      "field_type": 4,
+      "field_precision": -1,
+      "field_format": "",
+      "field_name": "VALID"
+    }
+  ],
+  "folder_name": ""
+}
diff --git 
a/integration-tests/transforms/metadata/unit-test/0099-standardize-phone-number-edge-cases
 UNIT.json 
b/integration-tests/transforms/metadata/unit-test/0099-standardize-phone-number-edge-cases
 UNIT.json
new file mode 100644
index 0000000000..073b94bcd8
--- /dev/null
+++ 
b/integration-tests/transforms/metadata/unit-test/0099-standardize-phone-number-edge-cases
 UNIT.json        
@@ -0,0 +1,48 @@
+{
+  "variableValues": [
+    {
+      "key": "OUTPUT_FIELD",
+      "value": "E164_OUT"
+    }
+  ],
+  "database_replacements": [],
+  "autoOpening": true,
+  "basePath": "",
+  "golden_data_sets": [
+    {
+      "field_mappings": [
+        {
+          "transform_field": "CASE",
+          "data_set_field": "CASE"
+        },
+        {
+          "transform_field": "E164_OUT",
+          "data_set_field": "E164_OUT"
+        },
+        {
+          "transform_field": "TYPE",
+          "data_set_field": "TYPE"
+        },
+        {
+          "transform_field": "VALID",
+          "data_set_field": "VALID"
+        }
+      ],
+      "field_order": [
+        "CASE",
+        "E164_OUT",
+        "TYPE",
+        "VALID"
+      ],
+      "transform_name": "Preview",
+      "data_set_name": "golden-standardize-phone-number"
+    }
+  ],
+  "input_data_sets": [],
+  "name": "0099-standardize-phone-number-edge-cases UNIT",
+  "description": "Standardize phone number edge cases: parse failure, empty 
number, country trim, variables",
+  "persist_filename": "",
+  "trans_test_tweaks": [],
+  "pipeline_filename": "./0099-standardize-phone-number-edge-cases.hpl",
+  "test_type": "UNIT_TEST"
+}
diff --git 
a/plugins/transforms/standardizephonenumber/src/main/java/org/apache/hop/pipeline/transforms/standardizephonenumber/StandardizePhoneNumber.java
 
b/plugins/transforms/standardizephonenumber/src/main/java/org/apache/hop/pipeline/transforms/standardizephonenumber/StandardizePhoneNumber.java
index f9da3af078..3c9f313539 100644
--- 
a/plugins/transforms/standardizephonenumber/src/main/java/org/apache/hop/pipeline/transforms/standardizephonenumber/StandardizePhoneNumber.java
+++ 
b/plugins/transforms/standardizephonenumber/src/main/java/org/apache/hop/pipeline/transforms/standardizephonenumber/StandardizePhoneNumber.java
@@ -22,6 +22,7 @@ import com.google.i18n.phonenumbers.PhoneNumberUtil;
 import com.google.i18n.phonenumbers.PhoneNumberUtil.PhoneNumberFormat;
 import com.google.i18n.phonenumbers.Phonenumber.PhoneNumber;
 import java.util.Arrays;
+import java.util.Locale;
 import java.util.Set;
 import org.apache.hop.core.exception.HopException;
 import org.apache.hop.core.row.IRowMeta;
@@ -36,6 +37,8 @@ public class StandardizePhoneNumber
     extends BaseTransform<StandardizePhoneNumberMeta, 
StandardizePhoneNumberData> {
   private static final Class<?> PKG = StandardizePhoneNumber.class;
 
+  static final String NUMBER_TYPE_ERROR = "ERROR";
+
   private PhoneNumberUtil phoneNumberService;
   private Set<String> supportedRegions;
 
@@ -87,94 +90,61 @@ public class StandardizePhoneNumber
     Object[] outputRow = Arrays.copyOf(row, data.outputRowMeta.size());
 
     for (StandardizePhoneField standardize : meta.getFields()) {
+      String inputField = resolve(standardize.getInputField());
+      String outputField = resolve(standardize.getOutputField());
+      String countryField = resolve(standardize.getCountryField());
+      String numberTypeField = resolve(standardize.getNumberTypeField());
+      String isValidNumberField = resolve(standardize.getIsValidNumberField());
+      String numberFormat = resolve(standardize.getNumberFormat());
 
-      // Default region
-      String region = standardize.getDefaultCountry();
-      if (!Utils.isEmpty(standardize.getCountryField())) {
-
-        int index = inputRowMeta.indexOfValue(standardize.getCountryField());
-
-        // if country field not found
-        if (index < 0) {
-          logError(
-              BaseMessages.getString(
-                  PKG,
-                  "StandardizePhoneNumber.Log.CountryFieldNotFound",
-                  standardize.getCountryField()));
-          this.setErrors(1);
-          return false;
-        }
-
-        String country = inputRowMeta.getString(row, index);
-        if (country == null || Utils.isEmpty(country)) {
-          region = standardize.getDefaultCountry();
-        } else if (supportedRegions.contains(country.toUpperCase())) {
-          region = country.toUpperCase();
-        } else {
-          logError(
-              BaseMessages.getString(
-                  PKG, "StandardizePhoneNumber.Log.RegionNotSupported", 
country));
-          region = standardize.getDefaultCountry();
-        }
-      }
+      String region = resolveRegion(standardize, countryField, inputRowMeta, 
row);
 
-      // Parse phone number
-      String value = null;
-      int index = inputRowMeta.indexOfValue(standardize.getInputField());
+      int inputIndex = inputRowMeta.indexOfValue(inputField);
 
       // if input field not found
-      if (index < 0) {
+      if (inputIndex < 0) {
         this.logError(
             BaseMessages.getString(
-                PKG, "StandardizePhoneNumber.Log.InputFieldNotFound", 
standardize.getInputField()));
+                PKG, "StandardizePhoneNumber.Log.InputFieldNotFound", 
inputField));
         this.setErrors(1);
         return false;
       }
-      value = inputRowMeta.getString(row, index);
 
-      if (value != null && !Utils.isEmpty(value)) {
+      int outputIndex = inputIndex;
+      if (!Utils.isEmpty(outputField)) {
+        int resolvedOutputIndex = data.outputRowMeta.indexOfValue(outputField);
+        if (resolvedOutputIndex >= 0) {
+          outputIndex = resolvedOutputIndex;
+        }
+      }
+
+      String originalValue = inputRowMeta.getString(row, inputIndex);
+      if (!Utils.isEmpty(originalValue)) {
         PhoneNumber phoneNumber = null;
         try {
-          // Replace unsupported character wit blank
-          value = value.replace(',', ' ');
+          // Replace unsupported character with blank
+          String value = originalValue.replace(',', ' ');
 
-          // Format
-          PhoneNumberFormat format = 
getPhoneNumberFormat(standardize.getNumberFormat());
-
-          // Parse phone number
+          PhoneNumberFormat format = getPhoneNumberFormat(numberFormat);
           phoneNumber = phoneNumberService.parse(value, region);
-          if (!Utils.isEmpty(standardize.getOutputField())) {
-            index = 
data.outputRowMeta.indexOfValue(standardize.getOutputField());
-          }
-          outputRow[index] = phoneNumberService.format(phoneNumber, format);
+          outputRow[outputIndex] = phoneNumberService.format(phoneNumber, 
format);
         } catch (NumberParseException e) {
+          outputRow[outputIndex] = originalValue;
           if (isRowLevel()) {
             logRowlevel(
                 BaseMessages.getString(
                     PKG,
                     "StandardizePhoneNumber.Log.ProcessPhoneNumberError",
-                    standardize.getInputField(),
-                    value));
+                    inputField,
+                    originalValue));
           }
         }
 
-        if (!Utils.isEmpty(standardize.getNumberTypeField())) {
-          int i = 
data.outputRowMeta.indexOfValue(standardize.getNumberTypeField());
-          if (phoneNumber != null) {
-            outputRow[i] = 
phoneNumberService.getNumberType(phoneNumber).toString();
-          } else outputRow[i] = "ERROR";
-        }
-
-        if (!Utils.isEmpty(standardize.getIsValidNumberField())) {
-          int i = 
data.outputRowMeta.indexOfValue(standardize.getIsValidNumberField());
-          if (phoneNumber != null) outputRow[i] = 
phoneNumberService.isValidNumber(phoneNumber);
-          else outputRow[i] = false;
-        }
+        setNumberType(outputRow, numberTypeField, phoneNumber);
+        setIsValid(outputRow, isValidNumberField, phoneNumber);
       } else {
-        if (!Utils.isEmpty(standardize.getIsValidNumberField())) {
-          int i = 
data.outputRowMeta.indexOfValue(standardize.getIsValidNumberField());
-          outputRow[i] = false;
-        }
+        setNumberType(outputRow, numberTypeField, null);
+        setIsValid(outputRow, isValidNumberField, null);
       }
     }
 
@@ -196,6 +166,82 @@ public class StandardizePhoneNumber
     return true;
   }
 
+  /**
+   * Normalize a country / region code: trim whitespace and upper-case using 
{@link Locale#ROOT}.
+   *
+   * @param country raw country code, may be null
+   * @return ISO alpha-2 region or {@code null} if empty
+   */
+  static String normalizeRegion(String country) {
+    if (country == null) {
+      return null;
+    }
+    String normalized = country.trim().toUpperCase(Locale.ROOT);
+    return normalized.isEmpty() ? null : normalized;
+  }
+
+  private String resolveRegion(
+      StandardizePhoneField standardize, String countryField, IRowMeta 
inputRowMeta, Object[] row)
+      throws HopException {
+    String region = normalizeRegion(resolve(standardize.getDefaultCountry()));
+    if (Utils.isEmpty(countryField)) {
+      return region;
+    }
+
+    int index = inputRowMeta.indexOfValue(countryField);
+
+    // if country field not found
+    if (index < 0) {
+      String message =
+          BaseMessages.getString(
+              PKG, "StandardizePhoneNumber.Log.CountryFieldNotFound", 
countryField);
+      logError(message);
+      this.setErrors(1);
+      throw new HopException(message);
+    }
+
+    String country = inputRowMeta.getString(row, index);
+    String normalized = normalizeRegion(country);
+    if (normalized == null) {
+      return region;
+    }
+    if (supportedRegions.contains(normalized)) {
+      return normalized;
+    }
+    logError(BaseMessages.getString(PKG, 
"StandardizePhoneNumber.Log.RegionNotSupported", country));
+    return region;
+  }
+
+  private void setNumberType(Object[] outputRow, String numberTypeField, 
PhoneNumber phoneNumber) {
+    if (Utils.isEmpty(numberTypeField)) {
+      return;
+    }
+    int i = data.outputRowMeta.indexOfValue(numberTypeField);
+    if (i < 0) {
+      return;
+    }
+    if (phoneNumber != null) {
+      outputRow[i] = phoneNumberService.getNumberType(phoneNumber).toString();
+    } else {
+      outputRow[i] = NUMBER_TYPE_ERROR;
+    }
+  }
+
+  private void setIsValid(Object[] outputRow, String isValidNumberField, 
PhoneNumber phoneNumber) {
+    if (Utils.isEmpty(isValidNumberField)) {
+      return;
+    }
+    int i = data.outputRowMeta.indexOfValue(isValidNumberField);
+    if (i < 0) {
+      return;
+    }
+    if (phoneNumber != null) {
+      outputRow[i] = phoneNumberService.isValidNumber(phoneNumber);
+    } else {
+      outputRow[i] = false;
+    }
+  }
+
   @Override
   public boolean init() {
 
diff --git 
a/plugins/transforms/standardizephonenumber/src/main/java/org/apache/hop/pipeline/transforms/standardizephonenumber/StandardizePhoneNumberMeta.java
 
b/plugins/transforms/standardizephonenumber/src/main/java/org/apache/hop/pipeline/transforms/standardizephonenumber/StandardizePhoneNumberMeta.java
index 02d54e6164..e800d09ea7 100644
--- 
a/plugins/transforms/standardizephonenumber/src/main/java/org/apache/hop/pipeline/transforms/standardizephonenumber/StandardizePhoneNumberMeta.java
+++ 
b/plugins/transforms/standardizephonenumber/src/main/java/org/apache/hop/pipeline/transforms/standardizephonenumber/StandardizePhoneNumberMeta.java
@@ -92,35 +92,34 @@ public class StandardizePhoneNumberMeta
     try {
       // add the extra fields if specified
       for (StandardizePhoneField standardize : this.getFields()) {
+        String inputField = resolveName(variables, 
standardize.getInputField());
+        String outputField = resolveName(variables, 
standardize.getOutputField());
+        String numberTypeField = resolveName(variables, 
standardize.getNumberTypeField());
+        String isValidNumberField = resolveName(variables, 
standardize.getIsValidNumberField());
 
         // add the output fields if specified
-        int index = inputRowMeta.indexOfValue(standardize.getInputField());
-        IValueMeta valueMeta = inputRowMeta.getValueMeta(index);
-        if (!Utils.isEmpty(standardize.getOutputField())
-            && 
!standardize.getOutputField().equals(standardize.getInputField())) {
+        int index = inputRowMeta.indexOfValue(inputField);
+        IValueMeta valueMeta = index >= 0 ? inputRowMeta.getValueMeta(index) : 
null;
+        if (!Utils.isEmpty(outputField) && !outputField.equals(inputField)) {
           // created output field only if name changed
-          valueMeta =
-              ValueMetaFactory.createValueMeta(
-                  standardize.getOutputField(), IValueMeta.TYPE_STRING);
+          valueMeta = ValueMetaFactory.createValueMeta(outputField, 
IValueMeta.TYPE_STRING);
 
           inputRowMeta.addValueMeta(valueMeta);
         }
-        valueMeta.setOrigin(name);
+        if (valueMeta != null) {
+          valueMeta.setOrigin(name);
+        }
 
         // add result phone number type
-        if (!Utils.isEmpty(standardize.getNumberTypeField())) {
-          valueMeta =
-              ValueMetaFactory.createValueMeta(
-                  standardize.getNumberTypeField(), IValueMeta.TYPE_STRING);
+        if (!Utils.isEmpty(numberTypeField)) {
+          valueMeta = ValueMetaFactory.createValueMeta(numberTypeField, 
IValueMeta.TYPE_STRING);
           valueMeta.setOrigin(name);
           inputRowMeta.addValueMeta(valueMeta);
         }
 
         // add result is valid number
-        if (!Utils.isEmpty(standardize.getIsValidNumberField())) {
-          valueMeta =
-              ValueMetaFactory.createValueMeta(
-                  standardize.getIsValidNumberField(), 
IValueMeta.TYPE_BOOLEAN);
+        if (!Utils.isEmpty(isValidNumberField)) {
+          valueMeta = ValueMetaFactory.createValueMeta(isValidNumberField, 
IValueMeta.TYPE_BOOLEAN);
           valueMeta.setOrigin(name);
           inputRowMeta.addValueMeta(valueMeta);
         }
@@ -173,32 +172,35 @@ public class StandardizePhoneNumberMeta
 
       // Check only if input fields
       for (StandardizePhoneField standardize : fields) {
+        String inputField = resolveName(variables, 
standardize.getInputField());
+        String outputField = resolveName(variables, 
standardize.getOutputField());
+        String countryField = resolveName(variables, 
standardize.getCountryField());
 
         // See if there are missing input streams
         IValueMeta valueMeta = null;
         if (prev != null) {
-          valueMeta = prev.searchValueMeta(standardize.getInputField());
+          valueMeta = prev.searchValueMeta(inputField);
         }
         if (valueMeta == null) {
           String message =
               BaseMessages.getString(
                   PKG,
                   "StandardizePhoneNumberMeta.CheckResult.MissingInputField",
-                  Const.NVL(standardize.getInputField(), 
standardize.getOutputField()));
+                  Const.NVL(inputField, outputField));
           remarks.add(new CheckResult(ICheckResult.TYPE_RESULT_ERROR, message, 
transformMeta));
         }
 
-        // See if there are missing input streams
-        if (prev != null) {
-          valueMeta = prev.searchValueMeta(standardize.getCountryField());
-        }
-        if (valueMeta == null) {
-          String message =
-              BaseMessages.getString(
-                  PKG,
-                  "StandardizePhoneNumberMeta.CheckResult.MissingCountryField",
-                  standardize.getCountryField());
-          remarks.add(new CheckResult(ICheckResult.TYPE_RESULT_ERROR, message, 
transformMeta));
+        // Country field is optional when a default country is used
+        if (!Utils.isEmpty(countryField)) {
+          valueMeta = prev != null ? prev.searchValueMeta(countryField) : null;
+          if (valueMeta == null) {
+            String message =
+                BaseMessages.getString(
+                    PKG,
+                    
"StandardizePhoneNumberMeta.CheckResult.MissingCountryField",
+                    countryField);
+            remarks.add(new CheckResult(ICheckResult.TYPE_RESULT_ERROR, 
message, transformMeta));
+          }
         }
       }
 
@@ -242,4 +244,11 @@ public class StandardizePhoneNumberMeta
   public void setFields(final List<StandardizePhoneField> standardizes) {
     this.fields = standardizes;
   }
+
+  static String resolveName(IVariables variables, String name) {
+    if (Utils.isEmpty(name) || variables == null) {
+      return name;
+    }
+    return variables.resolve(name);
+  }
 }
diff --git 
a/plugins/transforms/standardizephonenumber/src/main/resources/org/apache/hop/pipeline/transforms/standardizephonenumber/messages/messages_en_US.properties
 
b/plugins/transforms/standardizephonenumber/src/main/resources/org/apache/hop/pipeline/transforms/standardizephonenumber/messages/messages_en_US.properties
index cff0ce4a11..b5c42802e9 100644
--- 
a/plugins/transforms/standardizephonenumber/src/main/resources/org/apache/hop/pipeline/transforms/standardizephonenumber/messages/messages_en_US.properties
+++ 
b/plugins/transforms/standardizephonenumber/src/main/resources/org/apache/hop/pipeline/transforms/standardizephonenumber/messages/messages_en_US.properties
@@ -21,6 +21,7 @@
 StandardizePhoneNumber.Description=Normalize phone number in a standardized 
and consistent manner using Google libphonenumber library.
 StandardizePhoneNumber.Injection.CountryField=Country field
 StandardizePhoneNumber.Injection.DefaultCountry=Default country
+StandardizePhoneNumber.Injection.Field=Field to standardize
 StandardizePhoneNumber.Injection.Fields=Fields to standardize
 StandardizePhoneNumber.Injection.InputField=Input field
 StandardizePhoneNumber.Injection.IsValidNumberField=Is valid
@@ -35,19 +36,19 @@ StandardizePhoneNumber.Log.StartedProcessing=Started 
transform processing
 StandardizePhoneNumber.Log.WroteRowToNextTransform=Wrote row to next 
transform: {0}
 StandardizePhoneNumber.Name=Standardize phone number
 StandardizePhoneNumberDialog.ColumnInfo.CountryField.Label=Country field
-StandardizePhoneNumberDialog.ColumnInfo.CountryField.Tooltip=The field holding 
the country code from the input data. The country code is required to be in the 
ISO alpha-2.
+StandardizePhoneNumberDialog.ColumnInfo.CountryField.Tooltip=The field holding 
the country code from the input data. The country code is required to be in the 
ISO alpha-2. Leading and trailing spaces are ignored. This field is optional 
when a default country is set.
 StandardizePhoneNumberDialog.ColumnInfo.DefaultCountry.Label=Default country
-StandardizePhoneNumberDialog.ColumnInfo.DefaultCountry.Tooltip=The default 
country code to use if input data from country field is empty or invalid
+StandardizePhoneNumberDialog.ColumnInfo.DefaultCountry.Tooltip=The default 
country code to use if the country field is empty, missing, or contains an 
unsupported value
 StandardizePhoneNumberDialog.ColumnInfo.InputField.Label=Input field
 StandardizePhoneNumberDialog.ColumnInfo.InputField.Tooltip=The field holding 
the phone numbers from the input data.
 StandardizePhoneNumberDialog.ColumnInfo.IsValidNumberField.Label=Is valid field
-StandardizePhoneNumberDialog.ColumnInfo.IsValidNumberField.Tooltip=The result 
field indicates whether a phone number is supposed to be valid
+StandardizePhoneNumberDialog.ColumnInfo.IsValidNumberField.Tooltip=The result 
field indicates whether a phone number is supposed to be valid. Empty or 
unparseable numbers are false.
 StandardizePhoneNumberDialog.ColumnInfo.NumberFormat.Label=Format
 StandardizePhoneNumberDialog.ColumnInfo.NumberFormat.Tooltip=The format to be 
used to standardize the phone number\n E164 +41446681800\n INTERNATIONAL +41 44 
668 18 00\n NATIONAL 044 668 18 00\n RFC3966 tel:+41-44-668-18-00
 StandardizePhoneNumberDialog.ColumnInfo.NumberTypeField.Label=Number type field
-StandardizePhoneNumberDialog.ColumnInfo.NumberTypeField.Tooltip=The result 
field indicates the type of a processed phone number, such as fixed line, 
mobile...
+StandardizePhoneNumberDialog.ColumnInfo.NumberTypeField.Tooltip=The result 
field indicates the type of a processed phone number, such as fixed line, 
mobile... Empty or unparseable numbers are ERROR.
 StandardizePhoneNumberDialog.ColumnInfo.OutputField.Label=Output field  
-StandardizePhoneNumberDialog.ColumnInfo.OutputField.Tooltip=Leave this field 
empty if you want to update the input field in stream\n. Otherwise a new field 
(string) will be added to the output data.
+StandardizePhoneNumberDialog.ColumnInfo.OutputField.Tooltip=Leave this field 
empty if you want to update the input field in stream\n. Otherwise a new field 
(string) will be added to the output data. Field names may contain variables. 
If parsing fails, the original value is copied to this field.
 StandardizePhoneNumberDialog.Fields.Label=Fields to standardize
 StandardizePhoneNumberDialog.Shell.Title=Standardize phone number
 StandardizePhoneNumberMeta.CheckResult.MissingCountryField=Missing country 
input field: [{0}]
diff --git 
a/plugins/transforms/standardizephonenumber/src/test/java/org/apache/hop/pipeline/transforms/standardizephonenumber/StandardizePhoneFieldTest.java
 
b/plugins/transforms/standardizephonenumber/src/test/java/org/apache/hop/pipeline/transforms/standardizephonenumber/StandardizePhoneFieldTest.java
new file mode 100644
index 0000000000..247371742f
--- /dev/null
+++ 
b/plugins/transforms/standardizephonenumber/src/test/java/org/apache/hop/pipeline/transforms/standardizephonenumber/StandardizePhoneFieldTest.java
@@ -0,0 +1,107 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.hop.pipeline.transforms.standardizephonenumber;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotSame;
+import static org.junit.jupiter.api.Assertions.assertNull;
+
+import com.google.i18n.phonenumbers.PhoneNumberUtil.PhoneNumberFormat;
+import org.junit.jupiter.api.Test;
+
+/** Unit test for {@link StandardizePhoneField} */
+class StandardizePhoneFieldTest {
+
+  @Test
+  void defaultsUseE164Format() {
+    StandardizePhoneField field = new StandardizePhoneField();
+    assertEquals(PhoneNumberFormat.E164.name(), field.getNumberFormat());
+    assertNull(field.getInputField());
+    assertNull(field.getOutputField());
+    assertNull(field.getCountryField());
+    assertNull(field.getNumberTypeField());
+    assertNull(field.getIsValidNumberField());
+  }
+
+  @Test
+  void settersStripBlankValuesToNull() {
+    StandardizePhoneField field = new StandardizePhoneField();
+    field.setInputField("  ");
+    field.setOutputField("");
+    field.setCountryField("\t");
+    field.setNumberTypeField("   ");
+    field.setIsValidNumberField(" ");
+
+    assertNull(field.getInputField());
+    assertNull(field.getOutputField());
+    assertNull(field.getCountryField());
+    assertNull(field.getNumberTypeField());
+    assertNull(field.getIsValidNumberField());
+  }
+
+  @Test
+  void settersTrimSurroundingWhitespace() {
+    StandardizePhoneField field = new StandardizePhoneField();
+    field.setInputField(" PHONE ");
+    field.setOutputField(" E164 ");
+    field.setCountryField(" COUNTRY ");
+    field.setNumberTypeField(" TYPE ");
+    field.setIsValidNumberField(" VALID ");
+
+    assertEquals("PHONE", field.getInputField());
+    assertEquals("E164", field.getOutputField());
+    assertEquals("COUNTRY", field.getCountryField());
+    assertEquals("TYPE", field.getNumberTypeField());
+    assertEquals("VALID", field.getIsValidNumberField());
+  }
+
+  @Test
+  void cloneCopiesAllProperties() {
+    StandardizePhoneField original = new StandardizePhoneField();
+    original.setInputField("PHONE");
+    original.setOutputField("PHONE_E164");
+    original.setNumberFormat("NATIONAL");
+    original.setCountryField("COUNTRY");
+    original.setDefaultCountry("BE");
+    original.setNumberTypeField("TYPE");
+    original.setIsValidNumberField("VALID");
+
+    StandardizePhoneField copy = (StandardizePhoneField) original.clone();
+
+    assertNotSame(original, copy);
+    assertEquals(original.getInputField(), copy.getInputField());
+    assertEquals(original.getOutputField(), copy.getOutputField());
+    assertEquals(original.getNumberFormat(), copy.getNumberFormat());
+    assertEquals(original.getCountryField(), copy.getCountryField());
+    assertEquals(original.getDefaultCountry(), copy.getDefaultCountry());
+    assertEquals(original.getNumberTypeField(), copy.getNumberTypeField());
+    assertEquals(original.getIsValidNumberField(), 
copy.getIsValidNumberField());
+  }
+
+  @Test
+  void copyConstructorMatchesClone() {
+    StandardizePhoneField original = new StandardizePhoneField();
+    original.setInputField("PHONE");
+    original.setOutputField("OUT");
+    original.setDefaultCountry("FR");
+
+    StandardizePhoneField copy = new StandardizePhoneField(original);
+    assertEquals("PHONE", copy.getInputField());
+    assertEquals("OUT", copy.getOutputField());
+    assertEquals("FR", copy.getDefaultCountry());
+  }
+}
diff --git 
a/plugins/transforms/standardizephonenumber/src/test/java/org/apache/hop/pipeline/transforms/standardizephonenumber/StandardizePhoneNumberMetaTest.java
 
b/plugins/transforms/standardizephonenumber/src/test/java/org/apache/hop/pipeline/transforms/standardizephonenumber/StandardizePhoneNumberMetaTest.java
index 58a6acb347..52316358d2 100644
--- 
a/plugins/transforms/standardizephonenumber/src/test/java/org/apache/hop/pipeline/transforms/standardizephonenumber/StandardizePhoneNumberMetaTest.java
+++ 
b/plugins/transforms/standardizephonenumber/src/test/java/org/apache/hop/pipeline/transforms/standardizephonenumber/StandardizePhoneNumberMetaTest.java
@@ -14,43 +14,320 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
 package org.apache.hop.pipeline.transforms.standardizephonenumber;
 
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import com.google.i18n.phonenumbers.PhoneNumberUtil.PhoneNumberFormat;
+import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.HashMap;
 import java.util.List;
-import java.util.Map;
-import org.apache.hop.core.exception.HopException;
-import org.apache.hop.junit.rules.RestoreHopEngineEnvironmentExtension;
-import org.apache.hop.pipeline.transforms.loadsave.LoadSaveTester;
-import 
org.apache.hop.pipeline.transforms.loadsave.validator.IFieldLoadSaveValidator;
-import org.junit.jupiter.api.extension.RegisterExtension;
+import org.apache.hop.core.HopClientEnvironment;
+import org.apache.hop.core.ICheckResult;
+import org.apache.hop.core.row.IValueMeta;
+import org.apache.hop.core.row.RowMeta;
+import org.apache.hop.core.row.value.ValueMetaString;
+import org.apache.hop.core.variables.Variables;
+import org.apache.hop.i18n.BaseMessages;
+import org.apache.hop.pipeline.PipelineMeta;
+import org.apache.hop.pipeline.transform.TransformMeta;
+import org.apache.hop.pipeline.transform.TransformSerializationTestUtil;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
 
+/** Unit test for {@link StandardizePhoneNumberMeta} */
 class StandardizePhoneNumberMetaTest {
-  @RegisterExtension
-  static RestoreHopEngineEnvironmentExtension env = new 
RestoreHopEngineEnvironmentExtension();
 
-  /**
-   * @throws HopException
-   */
-  public void test() throws HopException {
-    List<String> attributes = Arrays.asList("fields");
+  @BeforeEach
+  void setUp() throws Exception {
+    HopClientEnvironment.init();
+  }
+
+  @Test
+  void testSerializationRoundTrip() throws Exception {
+    StandardizePhoneNumberMeta meta =
+        TransformSerializationTestUtil.testSerialization(
+            "/standardize-phone-number-transform.xml", 
StandardizePhoneNumberMeta.class);
+
+    assertEquals(1, meta.getFields().size());
+    StandardizePhoneField field = meta.getFields().get(0);
+    assertEquals("PHONE", field.getInputField());
+    assertEquals("PHONE_CLEANED", field.getOutputField());
+    assertEquals("E164", field.getNumberFormat());
+    assertEquals("COUNTRY", field.getCountryField());
+    assertEquals("BE", field.getDefaultCountry());
+    assertEquals("TYPE", field.getNumberTypeField());
+    assertEquals("VALID", field.getIsValidNumberField());
+  }
+
+  @Test
+  void setDefaultLeavesFieldsEmpty() {
+    StandardizePhoneNumberMeta meta = new StandardizePhoneNumberMeta();
+    meta.setDefault();
+    assertTrue(meta.getFields().isEmpty());
+  }
+
+  @Test
+  void getAndSetFieldsRoundTrip() {
+    StandardizePhoneNumberMeta meta = new StandardizePhoneNumberMeta();
+    StandardizePhoneField field = new StandardizePhoneField();
+    field.setInputField("PHONE");
+    List<StandardizePhoneField> fields = List.of(field);
+    meta.setFields(fields);
+    assertEquals(fields, meta.getFields());
+  }
+
+  @Test
+  void checkDoesNotRequireCountryField() {
+    StandardizePhoneNumberMeta meta = metaWith("PHONE", "PHONE_E164", null);
+
+    List<ICheckResult> remarks = check(meta, phonePrev(), new String[] 
{"prev"});
+
+    assertFalse(
+        remarks.stream()
+            .anyMatch(
+                remark ->
+                    remark.getType() == ICheckResult.TYPE_RESULT_ERROR
+                        && remark.getText() != null
+                        && remark.getText().contains("country")),
+        "Optional country field should not produce a check error");
+  }
+
+  @Test
+  void checkReportsMissingCountryFieldWhenConfigured() {
+    StandardizePhoneNumberMeta meta = metaWith("PHONE", "PHONE_E164", 
"COUNTRY");
+
+    List<ICheckResult> remarks = check(meta, phonePrev(), new String[] 
{"prev"});
 
-    Map<String, String> getterMap = new HashMap<>();
-    Map<String, String> setterMap = new HashMap<>();
+    assertTrue(
+        remarks.stream()
+            .anyMatch(
+                remark ->
+                    remark.getType() == ICheckResult.TYPE_RESULT_ERROR
+                        && remark.getText() != null
+                        && remark.getText().contains("COUNTRY")));
+  }
+
+  @Test
+  void checkReportsMissingInputField() {
+    StandardizePhoneNumberMeta meta = metaWith("MISSING", "OUT", null);
+
+    List<ICheckResult> remarks = check(meta, phonePrev(), new String[] 
{"prev"});
+
+    assertTrue(
+        remarks.stream()
+            .anyMatch(
+                remark ->
+                    remark.getType() == ICheckResult.TYPE_RESULT_ERROR
+                        && remark.getText() != null
+                        && remark.getText().contains("MISSING")));
+  }
+
+  @Test
+  void checkWarnsWhenPreviousFieldsAreMissing() {
+    StandardizePhoneNumberMeta meta = new StandardizePhoneNumberMeta();
+
+    List<ICheckResult> remarks = check(meta, new RowMeta(), new String[] 
{"prev"});
+
+    assertTrue(
+        remarks.stream().anyMatch(remark -> remark.getType() == 
ICheckResult.TYPE_RESULT_WARNING));
+  }
+
+  @Test
+  void checkErrorsWhenNoInputStreams() {
+    StandardizePhoneNumberMeta meta = metaWith("PHONE", "OUT", null);
 
-    Map<String, IFieldLoadSaveValidator<?>> fieldLoadSaveValidatorAttributeMap 
= new HashMap<>();
+    List<ICheckResult> remarks = check(meta, phonePrev(), new String[] {});
 
-    LoadSaveTester<StandardizePhoneNumberMeta> loadSaveTester =
-        new LoadSaveTester<>(
+    String expected =
+        BaseMessages.getString(
             StandardizePhoneNumberMeta.class,
-            attributes,
-            getterMap,
-            setterMap,
-            fieldLoadSaveValidatorAttributeMap,
-            new HashMap<>());
+            
"StandardizePhoneNumberMeta.CheckResult.NotReceivingInfoFromOtherTransforms");
+    assertTrue(
+        remarks.stream()
+            .anyMatch(
+                remark ->
+                    remark.getType() == ICheckResult.TYPE_RESULT_ERROR
+                        && expected.equals(remark.getText())));
+  }
+
+  @Test
+  void checkOkWhenReceivingFieldsAndInput() {
+    StandardizePhoneNumberMeta meta = metaWith("PHONE", "OUT", "COUNTRY");
+    RowMeta prev = phonePrev();
+    prev.addValueMeta(new ValueMetaString("COUNTRY"));
+
+    List<ICheckResult> remarks = check(meta, prev, new String[] {"prev"});
+
+    assertTrue(
+        remarks.stream().anyMatch(remark -> remark.getType() == 
ICheckResult.TYPE_RESULT_OK));
+    assertFalse(
+        remarks.stream().anyMatch(remark -> remark.getType() == 
ICheckResult.TYPE_RESULT_ERROR));
+  }
+
+  @Test
+  void checkResolvesVariableFieldNames() {
+    StandardizePhoneNumberMeta meta = metaWith("${IN}", "OUT", "${COUNTRY}");
+    RowMeta prev = phonePrev();
+    prev.addValueMeta(new ValueMetaString("COUNTRY"));
+
+    Variables variables = new Variables();
+    variables.setVariable("IN", "PHONE");
+    variables.setVariable("COUNTRY", "COUNTRY");
+
+    List<ICheckResult> remarks = new ArrayList<>();
+    meta.check(
+        remarks,
+        new PipelineMeta(),
+        new TransformMeta("standardize", meta),
+        prev,
+        new String[] {"prev"},
+        new String[] {},
+        null,
+        variables,
+        null);
+
+    assertFalse(remarks.stream().anyMatch(r -> r.getType() == 
ICheckResult.TYPE_RESULT_ERROR));
+  }
+
+  @Test
+  void getFieldsResolvesOutputFieldVariables() throws Exception {
+    StandardizePhoneNumberMeta meta = new StandardizePhoneNumberMeta();
+    StandardizePhoneField field = new StandardizePhoneField();
+    field.setInputField("PHONE");
+    field.setOutputField("${OUTPUT_FIELD}");
+    field.setNumberTypeField("${TYPE_FIELD}");
+    field.setIsValidNumberField("${VALID_FIELD}");
+    meta.setFields(List.of(field));
+
+    Variables variables = new Variables();
+    variables.setVariable("OUTPUT_FIELD", "E164_OUT");
+    variables.setVariable("TYPE_FIELD", "TYPE");
+    variables.setVariable("VALID_FIELD", "VALID");
+
+    RowMeta rowMeta = new RowMeta();
+    rowMeta.addValueMeta(new ValueMetaString("PHONE"));
+    meta.getFields(rowMeta, "standardize", null, null, variables, null);
+
+    assertEquals("PHONE", rowMeta.getValueMeta(0).getName());
+    assertEquals("E164_OUT", rowMeta.getValueMeta(1).getName());
+    assertEquals(IValueMeta.TYPE_STRING, rowMeta.getValueMeta(1).getType());
+    assertEquals("standardize", rowMeta.getValueMeta(1).getOrigin());
+    assertEquals("TYPE", rowMeta.getValueMeta(2).getName());
+    assertEquals(IValueMeta.TYPE_STRING, rowMeta.getValueMeta(2).getType());
+    assertEquals("VALID", rowMeta.getValueMeta(3).getName());
+    assertEquals(IValueMeta.TYPE_BOOLEAN, rowMeta.getValueMeta(3).getType());
+  }
+
+  @Test
+  void getFieldsDoesNotAddOutputWhenOverwritingInput() throws Exception {
+    StandardizePhoneNumberMeta meta = metaWith("PHONE", "PHONE", null);
+
+    RowMeta rowMeta = phonePrev();
+    meta.getFields(rowMeta, "standardize", null, null, new Variables(), null);
+
+    assertEquals(1, rowMeta.size());
+    assertEquals("PHONE", rowMeta.getValueMeta(0).getName());
+    assertEquals("standardize", rowMeta.getValueMeta(0).getOrigin());
+  }
+
+  @Test
+  void getFieldsDoesNotAddEmptyOptionalFields() throws Exception {
+    StandardizePhoneNumberMeta meta = new StandardizePhoneNumberMeta();
+    StandardizePhoneField field = new StandardizePhoneField();
+    field.setInputField("PHONE");
+    field.setOutputField("E164");
+    meta.setFields(List.of(field));
+
+    RowMeta rowMeta = phonePrev();
+    meta.getFields(rowMeta, "standardize", null, null, new Variables(), null);
+
+    assertEquals(2, rowMeta.size());
+    assertEquals("PHONE", rowMeta.getValueMeta(0).getName());
+    assertEquals("E164", rowMeta.getValueMeta(1).getName());
+  }
+
+  @Test
+  void getFieldsAddsOutputWhenInputIsMissing() throws Exception {
+    StandardizePhoneNumberMeta meta = metaWith("MISSING", "E164", null);
+
+    RowMeta rowMeta = phonePrev();
+    meta.getFields(rowMeta, "standardize", null, null, new Variables(), null);
+
+    assertEquals("E164", rowMeta.searchValueMeta("E164").getName());
+    assertEquals("standardize", rowMeta.searchValueMeta("E164").getOrigin());
+  }
+
+  @Test
+  void getSupportedFormatsListsLibphonenumberFormats() {
+    String[] formats = new StandardizePhoneNumberMeta().getSupportedFormats();
+    assertArrayEquals(
+        new String[] {
+          PhoneNumberFormat.E164.name(),
+          PhoneNumberFormat.INTERNATIONAL.name(),
+          PhoneNumberFormat.NATIONAL.name(),
+          PhoneNumberFormat.RFC3966.name()
+        },
+        formats);
+  }
+
+  @Test
+  void getSupportedCountriesIsSortedAndIncludesCommonRegions() {
+    String[] countries = new 
StandardizePhoneNumberMeta().getSupportedCountries();
+    assertTrue(countries.length > 10);
+    assertTrue(Arrays.asList(countries).contains("BE"));
+    assertTrue(Arrays.asList(countries).contains("US"));
+    assertTrue(Arrays.asList(countries).contains("FR"));
+    String[] sorted = countries.clone();
+    Arrays.sort(sorted);
+    assertArrayEquals(sorted, countries);
+  }
+
+  @Test
+  void resolveNameHandlesNullsAndVariables() {
+    assertNull(StandardizePhoneNumberMeta.resolveName(new Variables(), null));
+    assertEquals("", StandardizePhoneNumberMeta.resolveName(new Variables(), 
""));
+    assertEquals("${X}", StandardizePhoneNumberMeta.resolveName(null, "${X}"));
+
+    Variables variables = new Variables();
+    variables.setVariable("X", "PHONE");
+    assertEquals("PHONE", StandardizePhoneNumberMeta.resolveName(variables, 
"${X}"));
+  }
+
+  private static StandardizePhoneNumberMeta metaWith(String input, String 
output, String country) {
+    StandardizePhoneNumberMeta meta = new StandardizePhoneNumberMeta();
+    StandardizePhoneField field = new StandardizePhoneField();
+    field.setInputField(input);
+    field.setOutputField(output);
+    field.setCountryField(country);
+    field.setDefaultCountry("BE");
+    meta.setFields(List.of(field));
+    return meta;
+  }
+
+  private static RowMeta phonePrev() {
+    RowMeta prev = new RowMeta();
+    prev.addValueMeta(new ValueMetaString("PHONE"));
+    return prev;
+  }
 
-    loadSaveTester.testSerialization();
+  private static List<ICheckResult> check(
+      StandardizePhoneNumberMeta meta, RowMeta prev, String[] input) {
+    List<ICheckResult> remarks = new ArrayList<>();
+    meta.check(
+        remarks,
+        new PipelineMeta(),
+        new TransformMeta("standardize", meta),
+        prev,
+        input,
+        new String[] {},
+        null,
+        new Variables(),
+        null);
+    return remarks;
   }
 }
diff --git 
a/plugins/transforms/standardizephonenumber/src/test/java/org/apache/hop/pipeline/transforms/standardizephonenumber/StandardizePhoneNumberTest.java
 
b/plugins/transforms/standardizephonenumber/src/test/java/org/apache/hop/pipeline/transforms/standardizephonenumber/StandardizePhoneNumberTest.java
new file mode 100644
index 0000000000..e832c2cd1c
--- /dev/null
+++ 
b/plugins/transforms/standardizephonenumber/src/test/java/org/apache/hop/pipeline/transforms/standardizephonenumber/StandardizePhoneNumberTest.java
@@ -0,0 +1,448 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.hop.pipeline.transforms.standardizephonenumber;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.when;
+
+import com.google.i18n.phonenumbers.PhoneNumberUtil;
+import com.google.i18n.phonenumbers.PhoneNumberUtil.PhoneNumberFormat;
+import com.google.i18n.phonenumbers.Phonenumber.PhoneNumber;
+import java.util.List;
+import java.util.Locale;
+import org.apache.hop.core.HopEnvironment;
+import org.apache.hop.core.IRowSet;
+import org.apache.hop.core.exception.HopException;
+import org.apache.hop.core.logging.ILoggingObject;
+import org.apache.hop.core.row.RowMeta;
+import org.apache.hop.core.row.value.ValueMetaString;
+import org.apache.hop.junit.rules.RestoreHopEngineEnvironmentExtension;
+import org.apache.hop.pipeline.PipelineTestingUtil;
+import org.apache.hop.pipeline.transforms.mock.TransformMockHelper;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+/** Unit test for {@link StandardizePhoneNumber} */
+class StandardizePhoneNumberTest {
+
+  @RegisterExtension
+  static RestoreHopEngineEnvironmentExtension env = new 
RestoreHopEngineEnvironmentExtension();
+
+  private TransformMockHelper<StandardizePhoneNumberMeta, 
StandardizePhoneNumberData> mockHelper;
+
+  @BeforeEach
+  void setUp() throws HopException {
+    HopEnvironment.init();
+    mockHelper =
+        new TransformMockHelper<>(
+            "StandardizePhoneNumber",
+            StandardizePhoneNumberMeta.class,
+            StandardizePhoneNumberData.class);
+    when(mockHelper.logChannelFactory.create(any(), any(ILoggingObject.class)))
+        .thenReturn(mockHelper.iLogChannel);
+    when(mockHelper.pipeline.isRunning()).thenReturn(true);
+  }
+
+  @AfterEach
+  void tearDown() {
+    mockHelper.cleanUp();
+  }
+
+  @Test
+  void normalizeRegionTrimsAndUsesRootLocale() {
+    assertEquals("BE", StandardizePhoneNumber.normalizeRegion("BE"));
+    assertEquals("BE", StandardizePhoneNumber.normalizeRegion("be"));
+    assertEquals("BE", StandardizePhoneNumber.normalizeRegion(" BE "));
+    assertNull(StandardizePhoneNumber.normalizeRegion("   "));
+    assertNull(StandardizePhoneNumber.normalizeRegion(null));
+
+    Locale original = Locale.getDefault();
+    try {
+      Locale.setDefault(Locale.forLanguageTag("tr"));
+      assertEquals("IN", StandardizePhoneNumber.normalizeRegion("in"));
+    } finally {
+      Locale.setDefault(original);
+    }
+  }
+
+  @Test
+  void processRowFormatsValidNumber() throws Exception {
+    List<Object[]> rows = execute(createMeta("PHONE_CLEANED"), new Object[] 
{"0499500158", "BE"});
+
+    Object[] out = rows.get(0);
+    assertEquals("0499500158", out[0]);
+    assertEquals("BE", out[1]);
+    assertEquals("+32499500158", out[2]);
+    assertEquals("MOBILE", out[3]);
+    assertEquals(Boolean.TRUE, out[4]);
+  }
+
+  @Test
+  void processRowKeepsOriginalOnParseFailure() throws Exception {
+    List<Object[]> rows = execute(createMeta("PHONE_CLEANED"), new Object[] 
{"error", "BE"});
+
+    Object[] out = rows.get(0);
+    assertEquals("error", out[2]);
+    assertEquals(StandardizePhoneNumber.NUMBER_TYPE_ERROR, out[3]);
+    assertEquals(Boolean.FALSE, out[4]);
+  }
+
+  @Test
+  void processRowSetsErrorTypeForEmptyNumber() throws Exception {
+    List<Object[]> rows = execute(createMeta("PHONE_CLEANED"), new Object[] 
{null, "BE"});
+
+    Object[] out = rows.get(0);
+    assertNull(out[2]);
+    assertEquals(StandardizePhoneNumber.NUMBER_TYPE_ERROR, out[3]);
+    assertEquals(Boolean.FALSE, out[4]);
+  }
+
+  @Test
+  void processRowSetsErrorTypeForEmptyStringNumber() throws Exception {
+    List<Object[]> rows = execute(createMeta("PHONE_CLEANED"), new Object[] 
{"", "BE"});
+
+    Object[] out = rows.get(0);
+    assertNull(out[2]);
+    assertEquals(StandardizePhoneNumber.NUMBER_TYPE_ERROR, out[3]);
+    assertEquals(Boolean.FALSE, out[4]);
+  }
+
+  @Test
+  void processRowKeepsWhitespaceOnlyNumberAsParseFailure() throws Exception {
+    List<Object[]> rows = execute(createMeta("PHONE_CLEANED"), new Object[] {" 
  ", "BE"});
+
+    Object[] out = rows.get(0);
+    assertEquals("   ", out[2]);
+    assertEquals(StandardizePhoneNumber.NUMBER_TYPE_ERROR, out[3]);
+    assertEquals(Boolean.FALSE, out[4]);
+  }
+
+  @Test
+  void processRowTrimsCountryCodeWithSpaces() throws Exception {
+    List<Object[]> rows = execute(createMeta("PHONE_CLEANED"), new Object[] 
{"0499500158", "BE "});
+
+    Object[] out = rows.get(0);
+    assertEquals("+32499500158", out[2]);
+    assertEquals("MOBILE", out[3]);
+    assertEquals(Boolean.TRUE, out[4]);
+  }
+
+  @Test
+  void processRowAcceptsLowercaseCountryCode() throws Exception {
+    List<Object[]> rows = execute(createMeta("PHONE_CLEANED"), new Object[] 
{"0499500158", "be"});
+
+    assertEquals("+32499500158", rows.get(0)[2]);
+  }
+
+  @Test
+  void processRowResolvesOutputFieldVariable() throws Exception {
+    StandardizePhoneNumberMeta meta = createMeta("${OUTPUT_FIELD}");
+    StandardizePhoneNumberData data = new StandardizePhoneNumberData();
+    StandardizePhoneNumber transform =
+        new StandardizePhoneNumber(
+            mockHelper.transformMeta, meta, data, 0, mockHelper.pipelineMeta, 
mockHelper.pipeline);
+    transform.init();
+    transform.setVariable("OUTPUT_FIELD", "E164_OUT");
+
+    RowMeta rowMeta = inputRowMeta();
+    IRowSet rowSet = mockHelper.getMockInputRowSet(new Object[] {"0499500158", 
"BE"});
+    when(rowSet.getRowMeta()).thenReturn(rowMeta);
+    transform.addRowSetToInputRowSets(rowSet);
+    transform.setInputRowMeta(rowMeta);
+
+    List<Object[]> rows = PipelineTestingUtil.execute(transform, 1, false);
+    assertEquals("+32499500158", rows.get(0)[2]);
+    assertEquals("E164_OUT", data.outputRowMeta.getValueMeta(2).getName());
+  }
+
+  @Test
+  void processRowResolvesTypeAndValidFieldVariables() throws Exception {
+    StandardizePhoneNumberMeta meta = createMeta("PHONE_CLEANED");
+    meta.getFields().get(0).setNumberTypeField("${TYPE_FIELD}");
+    meta.getFields().get(0).setIsValidNumberField("${VALID_FIELD}");
+
+    StandardizePhoneNumberData data = new StandardizePhoneNumberData();
+    StandardizePhoneNumber transform =
+        new StandardizePhoneNumber(
+            mockHelper.transformMeta, meta, data, 0, mockHelper.pipelineMeta, 
mockHelper.pipeline);
+    transform.init();
+    transform.setVariable("TYPE_FIELD", "TYPE");
+    transform.setVariable("VALID_FIELD", "VALID");
+
+    RowMeta rowMeta = inputRowMeta();
+    IRowSet rowSet = mockHelper.getMockInputRowSet(new Object[] {"0499500158", 
"BE"});
+    when(rowSet.getRowMeta()).thenReturn(rowMeta);
+    transform.addRowSetToInputRowSets(rowSet);
+    transform.setInputRowMeta(rowMeta);
+
+    List<Object[]> rows = PipelineTestingUtil.execute(transform, 1, false);
+    assertEquals("MOBILE", rows.get(0)[3]);
+    assertEquals(Boolean.TRUE, rows.get(0)[4]);
+    assertEquals("TYPE", data.outputRowMeta.getValueMeta(3).getName());
+    assertEquals("VALID", data.outputRowMeta.getValueMeta(4).getName());
+  }
+
+  @Test
+  void processRowUsesDefaultCountryWhenCountryFieldEmpty() throws Exception {
+    StandardizePhoneNumberMeta meta = createMeta("PHONE_CLEANED");
+    meta.getFields().get(0).setCountryField(null);
+
+    List<Object[]> rows = execute(meta, new Object[] {"0499500158", "US"});
+    assertEquals("+32499500158", rows.get(0)[2]);
+  }
+
+  @Test
+  void processRowUsesDefaultCountryWhenCountryValueBlank() throws Exception {
+    List<Object[]> rows = execute(createMeta("PHONE_CLEANED"), new Object[] 
{"0499500158", "  "});
+    assertEquals("+32499500158", rows.get(0)[2]);
+  }
+
+  @Test
+  void processRowFallsBackToDefaultForUnsupportedRegion() throws Exception {
+    List<Object[]> rows = execute(createMeta("PHONE_CLEANED"), new Object[] 
{"0499500158", "XX"});
+    assertEquals("+32499500158", rows.get(0)[2]);
+  }
+
+  @Test
+  void processRowTrimsDefaultCountry() throws Exception {
+    StandardizePhoneNumberMeta meta = createMeta("PHONE_CLEANED");
+    meta.getFields().get(0).setCountryField(null);
+    meta.getFields().get(0).setDefaultCountry(" be ");
+
+    List<Object[]> rows = execute(meta, new Object[] {"0499500158", "US"});
+    assertEquals("+32499500158", rows.get(0)[2]);
+  }
+
+  @Test
+  void processRowOverwritesInputWhenOutputMatchesInput() throws Exception {
+    StandardizePhoneNumberMeta meta = createMeta("PHONE");
+
+    List<Object[]> rows = execute(meta, new Object[] {"0499500158", "BE"});
+    Object[] out = rows.get(0);
+    assertEquals("+32499500158", out[0]);
+    assertEquals("MOBILE", out[2]);
+    assertEquals(Boolean.TRUE, out[3]);
+  }
+
+  @Test
+  void processRowOverwritesInputWhenOutputFieldBlank() throws Exception {
+    StandardizePhoneNumberMeta meta = createMeta(null);
+
+    List<Object[]> rows = execute(meta, new Object[] {"0499500158", "BE"});
+    assertEquals("+32499500158", rows.get(0)[0]);
+  }
+
+  @Test
+  void processRowAppliesAllSupportedFormats() throws Exception {
+    PhoneNumberUtil util = PhoneNumberUtil.getInstance();
+    PhoneNumber parsed = util.parse("0499500158", "BE");
+
+    for (PhoneNumberFormat format :
+        List.of(
+            PhoneNumberFormat.E164,
+            PhoneNumberFormat.INTERNATIONAL,
+            PhoneNumberFormat.NATIONAL,
+            PhoneNumberFormat.RFC3966)) {
+      StandardizePhoneNumberMeta meta = createMeta("PHONE_CLEANED");
+      meta.getFields().get(0).setNumberFormat(format.name());
+
+      List<Object[]> rows = execute(meta, new Object[] {"0499500158", "BE"});
+      assertEquals(util.format(parsed, format), rows.get(0)[2], format.name());
+    }
+  }
+
+  @Test
+  void processRowFallsBackToE164ForUnknownFormat() throws Exception {
+    StandardizePhoneNumberMeta meta = createMeta("PHONE_CLEANED");
+    meta.getFields().get(0).setNumberFormat("NOT_A_FORMAT");
+
+    List<Object[]> rows = execute(meta, new Object[] {"0499500158", "BE"});
+    assertEquals("+32499500158", rows.get(0)[2]);
+  }
+
+  @Test
+  void processRowReplacesCommaBeforeParsing() throws Exception {
+    List<Object[]> rows = execute(createMeta("PHONE_CLEANED"), new Object[] 
{"0499,500158", "BE"});
+    assertEquals("+32499500158", rows.get(0)[2]);
+  }
+
+  @Test
+  void processRowMarksParsedButInvalidNumber() throws Exception {
+    List<Object[]> rows = execute(createMeta("PHONE_CLEANED"), new Object[] 
{"999", "FR"});
+
+    Object[] out = rows.get(0);
+    assertEquals("+33999", out[2]);
+    assertEquals("UNKNOWN", out[3]);
+    assertEquals(Boolean.FALSE, out[4]);
+  }
+
+  @Test
+  void processRowSkipsOptionalTypeAndValidFields() throws Exception {
+    StandardizePhoneNumberMeta meta = createMeta("PHONE_CLEANED");
+    meta.getFields().get(0).setNumberTypeField(null);
+    meta.getFields().get(0).setIsValidNumberField(null);
+
+    List<Object[]> rows = execute(meta, new Object[] {"0499500158", "BE"});
+    assertEquals("+32499500158", rows.get(0)[2]);
+    assertEquals(3, rows.get(0).length);
+  }
+
+  @Test
+  void processRowStandardizesMultipleFields() throws Exception {
+    StandardizePhoneField first = field("PHONE", "E164", "COUNTRY", "BE");
+    StandardizePhoneField second = field("PHONE2", "E164_2", "COUNTRY", "BE");
+    second.setNumberTypeField("TYPE2");
+    second.setIsValidNumberField("VALID2");
+
+    StandardizePhoneNumberMeta meta = new StandardizePhoneNumberMeta();
+    meta.setFields(List.of(first, second));
+
+    RowMeta rowMeta = inputRowMeta();
+    rowMeta.addValueMeta(new ValueMetaString("PHONE2"));
+
+    List<Object[]> rows = execute(meta, rowMeta, new Object[] {"0499500158", 
"BE", "0470123456"});
+    Object[] out = rows.get(0);
+    assertEquals("+32499500158", out[3]);
+    assertEquals("+32470123456", out[6]);
+  }
+
+  @Test
+  void processRowReturnsFalseWhenInputFieldMissing() throws Exception {
+    StandardizePhoneNumberMeta meta = createMeta("PHONE_CLEANED");
+    meta.getFields().get(0).setInputField("MISSING");
+
+    StandardizePhoneNumber transform =
+        ready(meta, inputRowMeta(), new Object[] {"0499500158", "BE"});
+    assertFalse(transform.processRow());
+    assertEquals(1, transform.getErrors());
+  }
+
+  @Test
+  void processRowThrowsWhenCountryFieldMissing() {
+    StandardizePhoneNumberMeta meta = createMeta("PHONE_CLEANED");
+    meta.getFields().get(0).setCountryField("ISO");
+
+    StandardizePhoneNumber transform =
+        ready(meta, inputRowMeta(), new Object[] {"0499500158", "BE"});
+    HopException exception = assertThrows(HopException.class, 
transform::processRow);
+    assertTrue(exception.getMessage().contains("ISO"));
+    assertEquals(1, transform.getErrors());
+  }
+
+  @Test
+  void processRowReturnsFalseWhenNoMoreRows() throws Exception {
+    StandardizePhoneNumber transform = ready(createMeta("PHONE_CLEANED"), 
inputRowMeta());
+    assertFalse(transform.processRow());
+  }
+
+  @Test
+  void processRowHandlesMultipleInputRows() throws Exception {
+    List<Object[]> rows =
+        execute(
+            createMeta("PHONE_CLEANED"),
+            new Object[] {"0499500158", "BE"},
+            new Object[] {"error", "BE"});
+
+    assertEquals(2, rows.size());
+    assertEquals("+32499500158", rows.get(0)[2]);
+    assertEquals("error", rows.get(1)[2]);
+  }
+
+  @Test
+  void getPhoneNumberFormatParsesKnownValuesAndFallsBack() {
+    StandardizePhoneNumber transform = 
createTransform(createMeta("PHONE_CLEANED"));
+    assertEquals(PhoneNumberFormat.NATIONAL, 
transform.getPhoneNumberFormat("NATIONAL"));
+    assertEquals(PhoneNumberFormat.E164, 
transform.getPhoneNumberFormat("bogus"));
+    assertEquals(PhoneNumberFormat.E164, transform.getPhoneNumberFormat(null));
+  }
+
+  @Test
+  void initReturnsTrue() {
+    StandardizePhoneNumberData data = new StandardizePhoneNumberData();
+    StandardizePhoneNumber transform =
+        new StandardizePhoneNumber(
+            mockHelper.transformMeta,
+            createMeta("PHONE_CLEANED"),
+            data,
+            0,
+            mockHelper.pipelineMeta,
+            mockHelper.pipeline);
+    assertTrue(transform.init());
+  }
+
+  private static StandardizePhoneNumberMeta createMeta(String outputField) {
+    StandardizePhoneNumberMeta meta = new StandardizePhoneNumberMeta();
+    meta.setFields(List.of(field("PHONE", outputField, "COUNTRY", "BE")));
+    return meta;
+  }
+
+  private static StandardizePhoneField field(
+      String input, String output, String country, String defaultCountry) {
+    StandardizePhoneField result = new StandardizePhoneField();
+    result.setInputField(input);
+    result.setOutputField(output);
+    result.setCountryField(country);
+    result.setDefaultCountry(defaultCountry);
+    result.setNumberFormat("E164");
+    result.setNumberTypeField("TYPE");
+    result.setIsValidNumberField("VALID");
+    return result;
+  }
+
+  private static RowMeta inputRowMeta() {
+    RowMeta rowMeta = new RowMeta();
+    rowMeta.addValueMeta(new ValueMetaString("PHONE"));
+    rowMeta.addValueMeta(new ValueMetaString("COUNTRY"));
+    return rowMeta;
+  }
+
+  private StandardizePhoneNumber createTransform(StandardizePhoneNumberMeta 
meta) {
+    StandardizePhoneNumberData data = new StandardizePhoneNumberData();
+    StandardizePhoneNumber transform =
+        new StandardizePhoneNumber(
+            mockHelper.transformMeta, meta, data, 0, mockHelper.pipelineMeta, 
mockHelper.pipeline);
+    transform.init();
+    return transform;
+  }
+
+  private StandardizePhoneNumber ready(
+      StandardizePhoneNumberMeta meta, RowMeta rowMeta, Object[]... inputRows) 
{
+    StandardizePhoneNumber transform = createTransform(meta);
+    IRowSet rowSet = mockHelper.getMockInputRowSet(inputRows);
+    when(rowSet.getRowMeta()).thenReturn(rowMeta);
+    transform.addRowSetToInputRowSets(rowSet);
+    transform.setInputRowMeta(rowMeta);
+    return transform;
+  }
+
+  private List<Object[]> execute(StandardizePhoneNumberMeta meta, Object[]... 
inputRows)
+      throws Exception {
+    return execute(meta, inputRowMeta(), inputRows);
+  }
+
+  private List<Object[]> execute(
+      StandardizePhoneNumberMeta meta, RowMeta rowMeta, Object[]... inputRows) 
throws Exception {
+    return PipelineTestingUtil.execute(ready(meta, rowMeta, inputRows), 
inputRows.length, false);
+  }
+}
diff --git 
a/plugins/transforms/standardizephonenumber/src/test/resources/standardize-phone-number-transform.xml
 
b/plugins/transforms/standardizephonenumber/src/test/resources/standardize-phone-number-transform.xml
new file mode 100644
index 0000000000..3874c451ca
--- /dev/null
+++ 
b/plugins/transforms/standardizephonenumber/src/test/resources/standardize-phone-number-transform.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Licensed to the Apache Software Foundation (ASF) under one or more
+  ~ contributor license agreements.  See the NOTICE file distributed with
+  ~ this work for additional information regarding copyright ownership.
+  ~ The ASF licenses this file to You under the Apache License, Version 2.0
+  ~ (the "License"); you may not use this file except in compliance with
+  ~ the License.  You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  ~
+  -->
+<transform>
+  <fields>
+    <field>
+      <input>PHONE</input>
+      <output>PHONE_CLEANED</output>
+      <format>E164</format>
+      <country>COUNTRY</country>
+      <defaultCountry>BE</defaultCountry>
+      <numbertype>TYPE</numbertype>
+      <isvalidnumber>VALID</isvalidnumber>
+    </field>
+  </fields>
+</transform>

Reply via email to