Create common json_parser_utils functions

Create a json_parser_utils namespace containing common functions for
parsing JSON.

Extract the common functions from the JSON parsing code in the
phosphor-regulators and phosphor-power-sequencer applications.
Both applications have some identical parsing functions.

Create a common ConfigFileParserError class. The phosphor-regulators and
phosphor-power-sequencer applications both have an identical version of
this exception class.

Extract the common test cases from the two applications and put them in
a common location as well.

Summary:
* Common JSON parsing functions in
  phosphor-power-sequencer/src/config_file_parser.* and
  phosphor-regulators/src/config_file_parser.* moved to
  json_parser_utils.*
* Common test cases in
  phosphor-power-sequencer/test/config_file_parser_tests.cpp and
  phosphor-regulators/test/config_file_parser_tests.cpp moved to
  test/json_parser_utils_tests.cpp
* phosphor-power-sequencer/src/config_file_parser_error.hpp and
  phosphor-regulators/src/config_file_parser_error.hpp replaced with
  config_file_parser_error.hpp
* phosphor-power-sequencer/test/config_file_parser_error_tests.cpp and
  phosphor-regulators/test/config_file_parser_error_tests.cpp replaced
  with test/config_file_parser_error_tests.cpp

Tested:
* Ran automated test cases.

Change-Id: I35074c5e42d9e89def41ba8e729fe11c54ed8d27
Signed-off-by: Shawn McCarney <shawnmm@us.ibm.com>
diff --git a/phosphor-power-sequencer/src/config_file_parser_error.hpp b/config_file_parser_error.hpp
similarity index 94%
rename from phosphor-power-sequencer/src/config_file_parser_error.hpp
rename to config_file_parser_error.hpp
index 4c77952..8b3e16e 100644
--- a/phosphor-power-sequencer/src/config_file_parser_error.hpp
+++ b/config_file_parser_error.hpp
@@ -1,5 +1,5 @@
 /**
- * Copyright © 2024 IBM Corporation
+ * Copyright © 2025 IBM Corporation
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -19,7 +19,7 @@
 #include <filesystem>
 #include <string>
 
-namespace phosphor::power::sequencer
+namespace phosphor::power::util
 {
 
 /**
@@ -82,4 +82,4 @@
     const std::string error{};
 };
 
-} // namespace phosphor::power::sequencer
+} // namespace phosphor::power::util
diff --git a/json_parser_utils.cpp b/json_parser_utils.cpp
new file mode 100644
index 0000000..be34323
--- /dev/null
+++ b/json_parser_utils.cpp
@@ -0,0 +1,33 @@
+/**
+ * Copyright © 2025 IBM Corporation
+ *
+ * Licensed 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.
+ */
+
+#include "json_parser_utils.hpp"
+
+namespace phosphor::power::json_parser_utils
+{
+
+std::vector<uint8_t> parseHexByteArray(const nlohmann::json& element)
+{
+    verifyIsArray(element);
+    std::vector<uint8_t> values;
+    for (auto& valueElement : element)
+    {
+        values.emplace_back(parseHexByte(valueElement));
+    }
+    return values;
+}
+
+} // namespace phosphor::power::json_parser_utils
diff --git a/json_parser_utils.hpp b/json_parser_utils.hpp
new file mode 100644
index 0000000..6dba42b
--- /dev/null
+++ b/json_parser_utils.hpp
@@ -0,0 +1,340 @@
+/**
+ * Copyright © 2025 IBM Corporation
+ *
+ * Licensed 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.
+ */
+#pragma once
+
+#include <nlohmann/json.hpp>
+
+#include <cstdint>
+#include <stdexcept>
+#include <string>
+#include <vector>
+
+/**
+ * @namespace json_parser_utils
+ *
+ * Contains utility functions for parsing JSON data.
+ */
+namespace phosphor::power::json_parser_utils
+{
+
+/**
+ * Returns the specified property of the specified JSON element.
+ *
+ * Throws an invalid_argument exception if the property does not exist.
+ *
+ * @param element JSON element
+ * @param property property name
+ */
+#pragma GCC diagnostic push
+#if __GNUC__ >= 13
+#pragma GCC diagnostic ignored "-Wdangling-reference"
+#endif
+inline const nlohmann::json& getRequiredProperty(const nlohmann::json& element,
+                                                 const std::string& property)
+{
+    auto it = element.find(property);
+    if (it == element.end())
+    {
+        throw std::invalid_argument{"Required property missing: " + property};
+    }
+    return *it;
+}
+#pragma GCC diagnostic pop
+
+/**
+ * Parses a JSON element containing a bit position (from 0-7).
+ *
+ * Returns the corresponding C++ uint8_t value.
+ *
+ * Throws an exception if parsing fails.
+ *
+ * @param element JSON element
+ * @return uint8_t value
+ */
+inline uint8_t parseBitPosition(const nlohmann::json& element)
+{
+    // Verify element contains an integer
+    if (!element.is_number_integer())
+    {
+        throw std::invalid_argument{"Element is not an integer"};
+    }
+    int value = element.get<int>();
+    if ((value < 0) || (value > 7))
+    {
+        throw std::invalid_argument{"Element is not a bit position"};
+    }
+    return static_cast<uint8_t>(value);
+}
+
+/**
+ * Parses a JSON element containing a bit value (0 or 1).
+ *
+ * Returns the corresponding C++ uint8_t value.
+ *
+ * Throws an exception if parsing fails.
+ *
+ * @param element JSON element
+ * @return uint8_t value
+ */
+inline uint8_t parseBitValue(const nlohmann::json& element)
+{
+    // Verify element contains an integer
+    if (!element.is_number_integer())
+    {
+        throw std::invalid_argument{"Element is not an integer"};
+    }
+    int value = element.get<int>();
+    if ((value < 0) || (value > 1))
+    {
+        throw std::invalid_argument{"Element is not a bit value"};
+    }
+    return static_cast<uint8_t>(value);
+}
+
+/**
+ * Parses a JSON element containing a boolean.
+ *
+ * Returns the corresponding C++ boolean value.
+ *
+ * Throws an exception if parsing fails.
+ *
+ * @param element JSON element
+ * @return boolean value
+ */
+inline bool parseBoolean(const nlohmann::json& element)
+{
+    // Verify element contains a boolean
+    if (!element.is_boolean())
+    {
+        throw std::invalid_argument{"Element is not a boolean"};
+    }
+    return element.get<bool>();
+}
+
+/**
+ * Parses a JSON element containing a double (floating point number).
+ *
+ * Returns the corresponding C++ double value.
+ *
+ * Throws an exception if parsing fails.
+ *
+ * @param element JSON element
+ * @return double value
+ */
+inline double parseDouble(const nlohmann::json& element)
+{
+    // Verify element contains a number (integer or floating point)
+    if (!element.is_number())
+    {
+        throw std::invalid_argument{"Element is not a number"};
+    }
+    return element.get<double>();
+}
+
+/**
+ * Parses a JSON element containing a byte value expressed as a hexadecimal
+ * string.
+ *
+ * The JSON number data type does not support the hexadecimal format.  For this
+ * reason, hexadecimal byte values are stored as strings in the configuration
+ * file.
+ *
+ * Returns the corresponding C++ uint8_t value.
+ *
+ * Throws an exception if parsing fails.
+ *
+ * @param element JSON element
+ * @return uint8_t value
+ */
+inline uint8_t parseHexByte(const nlohmann::json& element)
+{
+    if (!element.is_string())
+    {
+        throw std::invalid_argument{"Element is not a string"};
+    }
+    std::string value = element.get<std::string>();
+
+    bool isHex = (value.compare(0, 2, "0x") == 0) && (value.size() > 2) &&
+                 (value.size() < 5) &&
+                 (value.find_first_not_of("0123456789abcdefABCDEF", 2) ==
+                  std::string::npos);
+    if (!isHex)
+    {
+        throw std::invalid_argument{"Element is not hexadecimal string"};
+    }
+    return static_cast<uint8_t>(std::stoul(value, nullptr, 0));
+}
+
+/**
+ * Parses a JSON element containing an array of byte values expressed as a
+ * hexadecimal strings.
+ *
+ * Returns the corresponding C++ uint8_t values.
+ *
+ * Throws an exception if parsing fails.
+ *
+ * @param element JSON element
+ * @return vector of uint8_t
+ */
+std::vector<uint8_t> parseHexByteArray(const nlohmann::json& element);
+
+/**
+ * Parses a JSON element containing an 8-bit signed integer.
+ *
+ * Returns the corresponding C++ int8_t value.
+ *
+ * Throws an exception if parsing fails.
+ *
+ * @param element JSON element
+ * @return int8_t value
+ */
+inline int8_t parseInt8(const nlohmann::json& element)
+{
+    // Verify element contains an integer
+    if (!element.is_number_integer())
+    {
+        throw std::invalid_argument{"Element is not an integer"};
+    }
+    int value = element.get<int>();
+    if ((value < INT8_MIN) || (value > INT8_MAX))
+    {
+        throw std::invalid_argument{"Element is not an 8-bit signed integer"};
+    }
+    return static_cast<int8_t>(value);
+}
+
+/**
+ * Parses a JSON element containing a string.
+ *
+ * Returns the corresponding C++ string.
+ *
+ * Throws an exception if parsing fails.
+ *
+ * @param element JSON element
+ * @param isEmptyValid indicates whether an empty string value is valid
+ * @return string value
+ */
+inline std::string parseString(const nlohmann::json& element,
+                               bool isEmptyValid = false)
+{
+    if (!element.is_string())
+    {
+        throw std::invalid_argument{"Element is not a string"};
+    }
+    std::string value = element.get<std::string>();
+    if (value.empty() && !isEmptyValid)
+    {
+        throw std::invalid_argument{"Element contains an empty string"};
+    }
+    return value;
+}
+
+/**
+ * Parses a JSON element containing an 8-bit unsigned integer.
+ *
+ * Returns the corresponding C++ uint8_t value.
+ *
+ * Throws an exception if parsing fails.
+ *
+ * @param element JSON element
+ * @return uint8_t value
+ */
+inline uint8_t parseUint8(const nlohmann::json& element)
+{
+    // Verify element contains an integer
+    if (!element.is_number_integer())
+    {
+        throw std::invalid_argument{"Element is not an integer"};
+    }
+    int value = element.get<int>();
+    if ((value < 0) || (value > UINT8_MAX))
+    {
+        throw std::invalid_argument{"Element is not an 8-bit unsigned integer"};
+    }
+    return static_cast<uint8_t>(value);
+}
+
+/**
+ * Parses a JSON element containing an unsigned integer.
+ *
+ * Returns the corresponding C++ unsigned int value.
+ *
+ * Throws an exception if parsing fails.
+ *
+ * @param element JSON element
+ * @return unsigned int value
+ */
+inline unsigned int parseUnsignedInteger(const nlohmann::json& element)
+{
+    // Verify element contains an unsigned integer
+    if (!element.is_number_unsigned())
+    {
+        throw std::invalid_argument{"Element is not an unsigned integer"};
+    }
+    return element.get<unsigned int>();
+}
+
+/**
+ * Verifies that the specified JSON element is a JSON array.
+ *
+ * Throws an invalid_argument exception if the element is not an array.
+ *
+ * @param element JSON element
+ */
+inline void verifyIsArray(const nlohmann::json& element)
+{
+    if (!element.is_array())
+    {
+        throw std::invalid_argument{"Element is not an array"};
+    }
+}
+
+/**
+ * Verifies that the specified JSON element is a JSON object.
+ *
+ * Throws an invalid_argument exception if the element is not an object.
+ *
+ * @param element JSON element
+ */
+inline void verifyIsObject(const nlohmann::json& element)
+{
+    if (!element.is_object())
+    {
+        throw std::invalid_argument{"Element is not an object"};
+    }
+}
+
+/**
+ * Verifies that the specified JSON element contains the expected number of
+ * properties.
+ *
+ * Throws an invalid_argument exception if the element contains a different
+ * number of properties.  This indicates the element contains an invalid
+ * property.
+ *
+ * @param element JSON element
+ * @param expectedCount expected number of properties in element
+ */
+inline void verifyPropertyCount(const nlohmann::json& element,
+                                unsigned int expectedCount)
+{
+    if (element.size() != expectedCount)
+    {
+        throw std::invalid_argument{"Element contains an invalid property"};
+    }
+}
+
+} // namespace phosphor::power::json_parser_utils
diff --git a/meson.build b/meson.build
index 7843cd3..0879db6 100644
--- a/meson.build
+++ b/meson.build
@@ -179,6 +179,7 @@
     'compatible_system_types_finder.cpp',
     'dbus_interfaces_finder.cpp',
     'gpio.cpp',
+    'json_parser_utils.cpp',
     'pmbus.cpp',
     'temporary_file.cpp',
     'temporary_subdirectory.cpp',
diff --git a/phosphor-power-sequencer/src/config_file_parser.cpp b/phosphor-power-sequencer/src/config_file_parser.cpp
index e8d68d5..07566ae 100644
--- a/phosphor-power-sequencer/src/config_file_parser.cpp
+++ b/phosphor-power-sequencer/src/config_file_parser.cpp
@@ -17,11 +17,16 @@
 #include "config_file_parser.hpp"
 
 #include "config_file_parser_error.hpp"
+#include "json_parser_utils.hpp"
 
+#include <cstdint>
 #include <exception>
 #include <fstream>
 #include <optional>
+#include <stdexcept>
 
+using namespace phosphor::power::json_parser_utils;
+using ConfigFileParserError = phosphor::power::util::ConfigFileParserError;
 using json = nlohmann::json;
 namespace fs = std::filesystem;
 
diff --git a/phosphor-power-sequencer/src/config_file_parser.hpp b/phosphor-power-sequencer/src/config_file_parser.hpp
index 7b28f2f..ee12690 100644
--- a/phosphor-power-sequencer/src/config_file_parser.hpp
+++ b/phosphor-power-sequencer/src/config_file_parser.hpp
@@ -19,10 +19,8 @@
 
 #include <nlohmann/json.hpp>
 
-#include <cstdint>
 #include <filesystem>
 #include <memory>
-#include <stdexcept>
 #include <string>
 #include <vector>
 
@@ -78,50 +76,6 @@
 {
 
 /**
- * Returns the specified property of the specified JSON element.
- *
- * Throws an invalid_argument exception if the property does not exist.
- *
- * @param element JSON element
- * @param property property name
- */
-#pragma GCC diagnostic push
-#if __GNUC__ >= 13
-#pragma GCC diagnostic ignored "-Wdangling-reference"
-#endif
-inline const nlohmann::json& getRequiredProperty(const nlohmann::json& element,
-                                                 const std::string& property)
-{
-    auto it = element.find(property);
-    if (it == element.end())
-    {
-        throw std::invalid_argument{"Required property missing: " + property};
-    }
-    return *it;
-}
-#pragma GCC diagnostic pop
-
-/**
- * Parses a JSON element containing a boolean.
- *
- * Returns the corresponding C++ boolean value.
- *
- * Throws an exception if parsing fails.
- *
- * @param element JSON element
- * @return boolean value
- */
-inline bool parseBoolean(const nlohmann::json& element)
-{
-    // Verify element contains a boolean
-    if (!element.is_boolean())
-    {
-        throw std::invalid_argument{"Element is not a boolean"};
-    }
-    return element.get<bool>();
-}
-
-/**
  * Parses a JSON element containing a GPIO.
  *
  * Returns the corresponding C++ GPIO object.
@@ -170,127 +124,6 @@
  */
 std::vector<std::unique_ptr<Rail>> parseRoot(const nlohmann::json& element);
 
-/**
- * Parses a JSON element containing a string.
- *
- * Returns the corresponding C++ string.
- *
- * Throws an exception if parsing fails.
- *
- * @param element JSON element
- * @param isEmptyValid indicates whether an empty string value is valid
- * @return string value
- */
-inline std::string parseString(const nlohmann::json& element,
-                               bool isEmptyValid = false)
-{
-    if (!element.is_string())
-    {
-        throw std::invalid_argument{"Element is not a string"};
-    }
-    std::string value = element.get<std::string>();
-    if (value.empty() && !isEmptyValid)
-    {
-        throw std::invalid_argument{"Element contains an empty string"};
-    }
-    return value;
-}
-
-/**
- * Parses a JSON element containing an 8-bit unsigned integer.
- *
- * Returns the corresponding C++ uint8_t value.
- *
- * Throws an exception if parsing fails.
- *
- * @param element JSON element
- * @return uint8_t value
- */
-inline uint8_t parseUint8(const nlohmann::json& element)
-{
-    // Verify element contains an integer
-    if (!element.is_number_integer())
-    {
-        throw std::invalid_argument{"Element is not an integer"};
-    }
-    int value = element.get<int>();
-    if ((value < 0) || (value > UINT8_MAX))
-    {
-        throw std::invalid_argument{"Element is not an 8-bit unsigned integer"};
-    }
-    return static_cast<uint8_t>(value);
-}
-
-/**
- * Parses a JSON element containing an unsigned integer.
- *
- * Returns the corresponding C++ unsigned int value.
- *
- * Throws an exception if parsing fails.
- *
- * @param element JSON element
- * @return unsigned int value
- */
-inline unsigned int parseUnsignedInteger(const nlohmann::json& element)
-{
-    // Verify element contains an unsigned integer
-    if (!element.is_number_unsigned())
-    {
-        throw std::invalid_argument{"Element is not an unsigned integer"};
-    }
-    return element.get<unsigned int>();
-}
-
-/**
- * Verifies that the specified JSON element is a JSON array.
- *
- * Throws an invalid_argument exception if the element is not an array.
- *
- * @param element JSON element
- */
-inline void verifyIsArray(const nlohmann::json& element)
-{
-    if (!element.is_array())
-    {
-        throw std::invalid_argument{"Element is not an array"};
-    }
-}
-
-/**
- * Verifies that the specified JSON element is a JSON object.
- *
- * Throws an invalid_argument exception if the element is not an object.
- *
- * @param element JSON element
- */
-inline void verifyIsObject(const nlohmann::json& element)
-{
-    if (!element.is_object())
-    {
-        throw std::invalid_argument{"Element is not an object"};
-    }
-}
-
-/**
- * Verifies that the specified JSON element contains the expected number of
- * properties.
- *
- * Throws an invalid_argument exception if the element contains a different
- * number of properties.  This indicates the element contains an invalid
- * property.
- *
- * @param element JSON element
- * @param expectedCount expected number of properties in element
- */
-inline void verifyPropertyCount(const nlohmann::json& element,
-                                unsigned int expectedCount)
-{
-    if (element.size() != expectedCount)
-    {
-        throw std::invalid_argument{"Element contains an invalid property"};
-    }
-}
-
 } // namespace internal
 
 } // namespace phosphor::power::sequencer::config_file_parser
diff --git a/phosphor-power-sequencer/test/config_file_parser_error_tests.cpp b/phosphor-power-sequencer/test/config_file_parser_error_tests.cpp
deleted file mode 100644
index 9af389a..0000000
--- a/phosphor-power-sequencer/test/config_file_parser_error_tests.cpp
+++ /dev/null
@@ -1,53 +0,0 @@
-/**
- * Copyright © 2024 IBM Corporation
- *
- * Licensed 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.
- */
-#include "config_file_parser_error.hpp"
-
-#include <filesystem>
-
-#include <gtest/gtest.h>
-
-using namespace phosphor::power::sequencer;
-
-TEST(ConfigFileParserErrorTests, Constructor)
-{
-    std::filesystem::path pathName{
-        "/usr/share/phosphor-power-sequencer/foo.json"};
-    ConfigFileParserError error{pathName, "Required property missing"};
-    EXPECT_EQ(error.getPathName(), pathName);
-    EXPECT_STREQ(
-        error.what(),
-        "ConfigFileParserError: /usr/share/phosphor-power-sequencer/foo.json: "
-        "Required property missing");
-}
-
-TEST(ConfigFileParserErrorTests, GetPathName)
-{
-    std::filesystem::path pathName{
-        "/usr/share/phosphor-power-sequencer/bar.json"};
-    ConfigFileParserError error{pathName, "Required property missing"};
-    EXPECT_EQ(error.getPathName(), pathName);
-}
-
-TEST(ConfigFileParserErrorTests, What)
-{
-    std::filesystem::path pathName{
-        "/usr/share/phosphor-power-sequencer/bar.json"};
-    ConfigFileParserError error{pathName, "Required property missing"};
-    EXPECT_STREQ(
-        error.what(),
-        "ConfigFileParserError: /usr/share/phosphor-power-sequencer/bar.json: "
-        "Required property missing");
-}
diff --git a/phosphor-power-sequencer/test/config_file_parser_tests.cpp b/phosphor-power-sequencer/test/config_file_parser_tests.cpp
index 6f8a878..9ad21c8 100644
--- a/phosphor-power-sequencer/test/config_file_parser_tests.cpp
+++ b/phosphor-power-sequencer/test/config_file_parser_tests.cpp
@@ -23,7 +23,6 @@
 
 #include <nlohmann/json.hpp>
 
-#include <cstdint>
 #include <exception>
 #include <filesystem>
 #include <fstream>
@@ -322,57 +321,6 @@
     }
 }
 
-TEST(ConfigFileParserTests, GetRequiredProperty)
-{
-    // Test where property exists
-    {
-        const json element = R"( { "name": "VDD_CPU0" } )"_json;
-        const json& propertyElement = getRequiredProperty(element, "name");
-        EXPECT_EQ(propertyElement.get<std::string>(), "VDD_CPU0");
-    }
-
-    // Test where property does not exist
-    try
-    {
-        const json element = R"( { "foo": 23 } )"_json;
-        getRequiredProperty(element, "name");
-        ADD_FAILURE() << "Should not have reached this line.";
-    }
-    catch (const std::invalid_argument& e)
-    {
-        EXPECT_STREQ(e.what(), "Required property missing: name");
-    }
-}
-
-TEST(ConfigFileParserTests, ParseBoolean)
-{
-    // Test where works: true
-    {
-        const json element = R"( true )"_json;
-        bool value = parseBoolean(element);
-        EXPECT_EQ(value, true);
-    }
-
-    // Test where works: false
-    {
-        const json element = R"( false )"_json;
-        bool value = parseBoolean(element);
-        EXPECT_EQ(value, false);
-    }
-
-    // Test where fails: Element is not a boolean
-    try
-    {
-        const json element = R"( 1 )"_json;
-        parseBoolean(element);
-        ADD_FAILURE() << "Should not have reached this line.";
-    }
-    catch (const std::invalid_argument& e)
-    {
-        EXPECT_STREQ(e.what(), "Element is not a boolean");
-    }
-}
-
 TEST(ConfigFileParserTests, ParseGPIO)
 {
     // Test where works: Only required properties specified
@@ -879,205 +827,3 @@
         EXPECT_STREQ(e.what(), "Element contains an invalid property");
     }
 }
-
-TEST(ConfigFileParserTests, ParseString)
-{
-    // Test where works: Empty string
-    {
-        const json element = "";
-        std::string value = parseString(element, true);
-        EXPECT_EQ(value, "");
-    }
-
-    // Test where works: Non-empty string
-    {
-        const json element = "vdd_cpu1";
-        std::string value = parseString(element, false);
-        EXPECT_EQ(value, "vdd_cpu1");
-    }
-
-    // Test where fails: Element is not a string
-    try
-    {
-        const json element = R"( { "foo": "bar" } )"_json;
-        parseString(element);
-        ADD_FAILURE() << "Should not have reached this line.";
-    }
-    catch (const std::invalid_argument& e)
-    {
-        EXPECT_STREQ(e.what(), "Element is not a string");
-    }
-
-    // Test where fails: Empty string
-    try
-    {
-        const json element = "";
-        parseString(element);
-        ADD_FAILURE() << "Should not have reached this line.";
-    }
-    catch (const std::invalid_argument& e)
-    {
-        EXPECT_STREQ(e.what(), "Element contains an empty string");
-    }
-}
-
-TEST(ConfigFileParserTests, ParseUint8)
-{
-    // Test where works: 0
-    {
-        const json element = R"( 0 )"_json;
-        uint8_t value = parseUint8(element);
-        EXPECT_EQ(value, 0);
-    }
-
-    // Test where works: UINT8_MAX
-    {
-        const json element = R"( 255 )"_json;
-        uint8_t value = parseUint8(element);
-        EXPECT_EQ(value, 255);
-    }
-
-    // Test where fails: Element is not an integer
-    try
-    {
-        const json element = R"( 1.03 )"_json;
-        parseUint8(element);
-        ADD_FAILURE() << "Should not have reached this line.";
-    }
-    catch (const std::invalid_argument& e)
-    {
-        EXPECT_STREQ(e.what(), "Element is not an integer");
-    }
-
-    // Test where fails: Value < 0
-    try
-    {
-        const json element = R"( -1 )"_json;
-        parseUint8(element);
-        ADD_FAILURE() << "Should not have reached this line.";
-    }
-    catch (const std::invalid_argument& e)
-    {
-        EXPECT_STREQ(e.what(), "Element is not an 8-bit unsigned integer");
-    }
-
-    // Test where fails: Value > UINT8_MAX
-    try
-    {
-        const json element = R"( 256 )"_json;
-        parseUint8(element);
-        ADD_FAILURE() << "Should not have reached this line.";
-    }
-    catch (const std::invalid_argument& e)
-    {
-        EXPECT_STREQ(e.what(), "Element is not an 8-bit unsigned integer");
-    }
-}
-
-TEST(ConfigFileParserTests, ParseUnsignedInteger)
-{
-    // Test where works: 1
-    {
-        const json element = R"( 1 )"_json;
-        unsigned int value = parseUnsignedInteger(element);
-        EXPECT_EQ(value, 1);
-    }
-
-    // Test where fails: Element is not an integer
-    try
-    {
-        const json element = R"( 1.5 )"_json;
-        parseUnsignedInteger(element);
-        ADD_FAILURE() << "Should not have reached this line.";
-    }
-    catch (const std::invalid_argument& e)
-    {
-        EXPECT_STREQ(e.what(), "Element is not an unsigned integer");
-    }
-
-    // Test where fails: Value < 0
-    try
-    {
-        const json element = R"( -1 )"_json;
-        parseUnsignedInteger(element);
-        ADD_FAILURE() << "Should not have reached this line.";
-    }
-    catch (const std::invalid_argument& e)
-    {
-        EXPECT_STREQ(e.what(), "Element is not an unsigned integer");
-    }
-}
-
-TEST(ConfigFileParserTests, VerifyIsArray)
-{
-    // Test where element is an array
-    {
-        const json element = R"( [ "foo", "bar" ] )"_json;
-        verifyIsArray(element);
-    }
-
-    // Test where element is not an array
-    try
-    {
-        const json element = R"( { "foo": "bar" } )"_json;
-        verifyIsArray(element);
-        ADD_FAILURE() << "Should not have reached this line.";
-    }
-    catch (const std::invalid_argument& e)
-    {
-        EXPECT_STREQ(e.what(), "Element is not an array");
-    }
-}
-
-TEST(ConfigFileParserTests, VerifyIsObject)
-{
-    // Test where element is an object
-    {
-        const json element = R"( { "foo": "bar" } )"_json;
-        verifyIsObject(element);
-    }
-
-    // Test where element is not an object
-    try
-    {
-        const json element = R"( [ "foo", "bar" ] )"_json;
-        verifyIsObject(element);
-        ADD_FAILURE() << "Should not have reached this line.";
-    }
-    catch (const std::invalid_argument& e)
-    {
-        EXPECT_STREQ(e.what(), "Element is not an object");
-    }
-}
-
-TEST(ConfigFileParserTests, VerifyPropertyCount)
-{
-    // Test where element has expected number of properties
-    {
-        const json element = R"(
-            {
-                "line": 131,
-                "active_low": true
-            }
-        )"_json;
-        verifyPropertyCount(element, 2);
-    }
-
-    // Test where element has unexpected number of properties
-    try
-    {
-        const json element = R"(
-            {
-                "line": 131,
-                "active_low": true,
-                "foo": 1.3
-            }
-        )"_json;
-        verifyPropertyCount(element, 2);
-        ADD_FAILURE() << "Should not have reached this line.";
-    }
-    catch (const std::invalid_argument& e)
-    {
-        EXPECT_STREQ(e.what(), "Element contains an invalid property");
-    }
-}
diff --git a/phosphor-power-sequencer/test/meson.build b/phosphor-power-sequencer/test/meson.build
index 7bd92b7..2827c3f 100644
--- a/phosphor-power-sequencer/test/meson.build
+++ b/phosphor-power-sequencer/test/meson.build
@@ -3,7 +3,6 @@
     executable(
         'phosphor-power-sequencer-tests',
         'chassis_tests.cpp',
-        'config_file_parser_error_tests.cpp',
         'config_file_parser_tests.cpp',
         'pmbus_driver_device_tests.cpp',
         'rail_tests.cpp',
diff --git a/phosphor-regulators/src/config_file_parser.cpp b/phosphor-regulators/src/config_file_parser.cpp
index 442db04..b67d5cd 100644
--- a/phosphor-regulators/src/config_file_parser.cpp
+++ b/phosphor-regulators/src/config_file_parser.cpp
@@ -18,14 +18,19 @@
 
 #include "config_file_parser_error.hpp"
 #include "i2c_interface.hpp"
+#include "json_parser_utils.hpp"
 #include "pmbus_utils.hpp"
 
+#include <cstdint>
 #include <exception>
 #include <fstream>
 #include <optional>
+#include <stdexcept>
 #include <utility>
 
 using json = nlohmann::json;
+using ConfigFileParserError = phosphor::power::util::ConfigFileParserError;
+using namespace phosphor::power::json_parser_utils;
 
 namespace phosphor::power::regulators::config_file_parser
 {
@@ -439,17 +444,6 @@
     return devices;
 }
 
-std::vector<uint8_t> parseHexByteArray(const json& element)
-{
-    verifyIsArray(element);
-    std::vector<uint8_t> values;
-    for (auto& valueElement : element)
-    {
-        values.emplace_back(parseHexByte(valueElement));
-    }
-    return values;
-}
-
 std::unique_ptr<I2CCaptureBytesAction> parseI2CCaptureBytes(const json& element)
 {
     verifyIsObject(element);
diff --git a/phosphor-regulators/src/config_file_parser.hpp b/phosphor-regulators/src/config_file_parser.hpp
index 1119fc0..1c6fcee 100644
--- a/phosphor-regulators/src/config_file_parser.hpp
+++ b/phosphor-regulators/src/config_file_parser.hpp
@@ -48,10 +48,8 @@
 
 #include <nlohmann/json.hpp>
 
-#include <cstdint>
 #include <filesystem>
 #include <memory>
-#include <stdexcept>
 #include <string>
 #include <tuple>
 #include <vector>
@@ -80,30 +78,6 @@
 {
 
 /**
- * Returns the specified property of the specified JSON element.
- *
- * Throws an invalid_argument exception if the property does not exist.
- *
- * @param element JSON element
- * @param property property name
- */
-#pragma GCC diagnostic push
-#if __GNUC__ >= 13
-#pragma GCC diagnostic ignored "-Wdangling-reference"
-#endif
-inline const nlohmann::json& getRequiredProperty(const nlohmann::json& element,
-                                                 const std::string& property)
-{
-    auto it = element.find(property);
-    if (it == element.end())
-    {
-        throw std::invalid_argument{"Required property missing: " + property};
-    }
-    return *it;
-}
-#pragma GCC diagnostic pop
-
-/**
  * Parses a JSON element containing an action.
  *
  * Returns the corresponding C++ Action object.
@@ -141,76 +115,6 @@
 std::unique_ptr<AndAction> parseAnd(const nlohmann::json& element);
 
 /**
- * Parses a JSON element containing a bit position (from 0-7).
- *
- * Returns the corresponding C++ uint8_t value.
- *
- * Throws an exception if parsing fails.
- *
- * @param element JSON element
- * @return uint8_t value
- */
-inline uint8_t parseBitPosition(const nlohmann::json& element)
-{
-    // Verify element contains an integer
-    if (!element.is_number_integer())
-    {
-        throw std::invalid_argument{"Element is not an integer"};
-    }
-    int value = element.get<int>();
-    if ((value < 0) || (value > 7))
-    {
-        throw std::invalid_argument{"Element is not a bit position"};
-    }
-    return static_cast<uint8_t>(value);
-}
-
-/**
- * Parses a JSON element containing a bit value (0 or 1).
- *
- * Returns the corresponding C++ uint8_t value.
- *
- * Throws an exception if parsing fails.
- *
- * @param element JSON element
- * @return uint8_t value
- */
-inline uint8_t parseBitValue(const nlohmann::json& element)
-{
-    // Verify element contains an integer
-    if (!element.is_number_integer())
-    {
-        throw std::invalid_argument{"Element is not an integer"};
-    }
-    int value = element.get<int>();
-    if ((value < 0) || (value > 1))
-    {
-        throw std::invalid_argument{"Element is not a bit value"};
-    }
-    return static_cast<uint8_t>(value);
-}
-
-/**
- * Parses a JSON element containing a boolean.
- *
- * Returns the corresponding C++ boolean value.
- *
- * Throws an exception if parsing fails.
- *
- * @param element JSON element
- * @return boolean value
- */
-inline bool parseBoolean(const nlohmann::json& element)
-{
-    // Verify element contains a boolean
-    if (!element.is_boolean())
-    {
-        throw std::invalid_argument{"Element is not a boolean"};
-    }
-    return element.get<bool>();
-}
-
-/**
  * Parses a JSON element containing a chassis.
  *
  * Returns the corresponding C++ Chassis object.
@@ -300,73 +204,6 @@
     const nlohmann::json& element);
 
 /**
- * Parses a JSON element containing a double (floating point number).
- *
- * Returns the corresponding C++ double value.
- *
- * Throws an exception if parsing fails.
- *
- * @param element JSON element
- * @return double value
- */
-inline double parseDouble(const nlohmann::json& element)
-{
-    // Verify element contains a number (integer or floating point)
-    if (!element.is_number())
-    {
-        throw std::invalid_argument{"Element is not a number"};
-    }
-    return element.get<double>();
-}
-
-/**
- * Parses a JSON element containing a byte value expressed as a hexadecimal
- * string.
- *
- * The JSON number data type does not support the hexadecimal format.  For this
- * reason, hexadecimal byte values are stored as strings in the configuration
- * file.
- *
- * Returns the corresponding C++ uint8_t value.
- *
- * Throws an exception if parsing fails.
- *
- * @param element JSON element
- * @return uint8_t value
- */
-inline uint8_t parseHexByte(const nlohmann::json& element)
-{
-    if (!element.is_string())
-    {
-        throw std::invalid_argument{"Element is not a string"};
-    }
-    std::string value = element.get<std::string>();
-
-    bool isHex = (value.compare(0, 2, "0x") == 0) && (value.size() > 2) &&
-                 (value.size() < 5) &&
-                 (value.find_first_not_of("0123456789abcdefABCDEF", 2) ==
-                  std::string::npos);
-    if (!isHex)
-    {
-        throw std::invalid_argument{"Element is not hexadecimal string"};
-    }
-    return static_cast<uint8_t>(std::stoul(value, nullptr, 0));
-}
-
-/**
- * Parses a JSON element containing an array of byte values expressed as a
- * hexadecimal strings.
- *
- * Returns the corresponding C++ uint8_t values.
- *
- * Throws an exception if parsing fails.
- *
- * @param element JSON element
- * @return vector of uint8_t
- */
-std::vector<uint8_t> parseHexByteArray(const nlohmann::json& element);
-
-/**
  * Parses a JSON element containing an i2c_capture_bytes action.
  *
  * Returns the corresponding C++ I2CCaptureBytesAction object.
@@ -483,31 +320,6 @@
 std::unique_ptr<IfAction> parseIf(const nlohmann::json& element);
 
 /**
- * Parses a JSON element containing an 8-bit signed integer.
- *
- * Returns the corresponding C++ int8_t value.
- *
- * Throws an exception if parsing fails.
- *
- * @param element JSON element
- * @return int8_t value
- */
-inline int8_t parseInt8(const nlohmann::json& element)
-{
-    // Verify element contains an integer
-    if (!element.is_number_integer())
-    {
-        throw std::invalid_argument{"Element is not an integer"};
-    }
-    int value = element.get<int>();
-    if ((value < INT8_MIN) || (value > INT8_MAX))
-    {
-        throw std::invalid_argument{"Element is not an 8-bit signed integer"};
-    }
-    return static_cast<int8_t>(value);
-}
-
-/**
  * Parses a JSON element containing a relative inventory path.
  *
  * Returns the corresponding C++ string containing the absolute inventory path.
@@ -769,77 +581,6 @@
 std::unique_ptr<SetDeviceAction> parseSetDevice(const nlohmann::json& element);
 
 /**
- * Parses a JSON element containing a string.
- *
- * Returns the corresponding C++ string.
- *
- * Throws an exception if parsing fails.
- *
- * @param element JSON element
- * @param isEmptyValid indicates whether an empty string value is valid
- * @return string value
- */
-inline std::string parseString(const nlohmann::json& element,
-                               bool isEmptyValid = false)
-{
-    if (!element.is_string())
-    {
-        throw std::invalid_argument{"Element is not a string"};
-    }
-    std::string value = element.get<std::string>();
-    if (value.empty() && !isEmptyValid)
-    {
-        throw std::invalid_argument{"Element contains an empty string"};
-    }
-    return value;
-}
-
-/**
- * Parses a JSON element containing an 8-bit unsigned integer.
- *
- * Returns the corresponding C++ uint8_t value.
- *
- * Throws an exception if parsing fails.
- *
- * @param element JSON element
- * @return uint8_t value
- */
-inline uint8_t parseUint8(const nlohmann::json& element)
-{
-    // Verify element contains an integer
-    if (!element.is_number_integer())
-    {
-        throw std::invalid_argument{"Element is not an integer"};
-    }
-    int value = element.get<int>();
-    if ((value < 0) || (value > UINT8_MAX))
-    {
-        throw std::invalid_argument{"Element is not an 8-bit unsigned integer"};
-    }
-    return static_cast<uint8_t>(value);
-}
-
-/**
- * Parses a JSON element containing an unsigned integer.
- *
- * Returns the corresponding C++ unsigned int value.
- *
- * Throws an exception if parsing fails.
- *
- * @param element JSON element
- * @return unsigned int value
- */
-inline unsigned int parseUnsignedInteger(const nlohmann::json& element)
-{
-    // Verify element contains an unsigned integer
-    if (!element.is_number_unsigned())
-    {
-        throw std::invalid_argument{"Element is not an unsigned integer"};
-    }
-    return element.get<unsigned int>();
-}
-
-/**
  * Parses a JSON element containing a VoutDataFormat expressed as a string.
  *
  * Returns the corresponding VoutDataFormat enum value.
@@ -851,56 +592,6 @@
  */
 pmbus_utils::VoutDataFormat parseVoutDataFormat(const nlohmann::json& element);
 
-/**
- * Verifies that the specified JSON element is a JSON array.
- *
- * Throws an invalid_argument exception if the element is not an array.
- *
- * @param element JSON element
- */
-inline void verifyIsArray(const nlohmann::json& element)
-{
-    if (!element.is_array())
-    {
-        throw std::invalid_argument{"Element is not an array"};
-    }
-}
-
-/**
- * Verifies that the specified JSON element is a JSON object.
- *
- * Throws an invalid_argument exception if the element is not an object.
- *
- * @param element JSON element
- */
-inline void verifyIsObject(const nlohmann::json& element)
-{
-    if (!element.is_object())
-    {
-        throw std::invalid_argument{"Element is not an object"};
-    }
-}
-
-/**
- * Verifies that the specified JSON element contains the expected number of
- * properties.
- *
- * Throws an invalid_argument exception if the element contains a different
- * number of properties.  This indicates the element contains an invalid
- * property.
- *
- * @param element JSON element
- * @param expectedCount expected number of properties in element
- */
-inline void verifyPropertyCount(const nlohmann::json& element,
-                                unsigned int expectedCount)
-{
-    if (element.size() != expectedCount)
-    {
-        throw std::invalid_argument{"Element contains an invalid property"};
-    }
-}
-
 } // namespace internal
 
 } // namespace phosphor::power::regulators::config_file_parser
diff --git a/phosphor-regulators/src/config_file_parser_error.hpp b/phosphor-regulators/src/config_file_parser_error.hpp
deleted file mode 100644
index 9c783c5..0000000
--- a/phosphor-regulators/src/config_file_parser_error.hpp
+++ /dev/null
@@ -1,86 +0,0 @@
-/**
- * Copyright © 2020 IBM Corporation
- *
- * Licensed 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.
- */
-#pragma once
-
-#include <exception>
-#include <filesystem>
-#include <string>
-
-namespace phosphor::power::regulators
-{
-
-/**
- * @class ConfigFileParserError
- *
- * An error that occurred while parsing the JSON configuration file that
- * controls the phosphor-regulators application.
- */
-class ConfigFileParserError : public std::exception
-{
-  public:
-    // Specify which compiler-generated methods we want
-    ConfigFileParserError() = delete;
-    ConfigFileParserError(const ConfigFileParserError&) = default;
-    ConfigFileParserError(ConfigFileParserError&&) = default;
-    ConfigFileParserError& operator=(const ConfigFileParserError&) = default;
-    ConfigFileParserError& operator=(ConfigFileParserError&&) = default;
-    virtual ~ConfigFileParserError() = default;
-
-    /**
-     * Constructor.
-     *
-     * @param pathName configuration file path name
-     * @param error error message
-     */
-    explicit ConfigFileParserError(const std::filesystem::path& pathName,
-                                   const std::string& error) :
-        pathName{pathName},
-        error{"ConfigFileParserError: " + pathName.string() + ": " + error}
-    {}
-
-    /**
-     * Returns the configuration file path name.
-     *
-     * @return path name
-     */
-    const std::filesystem::path& getPathName()
-    {
-        return pathName;
-    }
-
-    /**
-     * Returns the description of this error.
-     *
-     * @return error description
-     */
-    const char* what() const noexcept override
-    {
-        return error.c_str();
-    }
-
-  private:
-    /**
-     * Configuration file path name.
-     */
-    const std::filesystem::path pathName;
-
-    /**
-     * Error message.
-     */
-    const std::string error{};
-};
-
-} // namespace phosphor::power::regulators
diff --git a/phosphor-regulators/src/error_logging_utils.cpp b/phosphor-regulators/src/error_logging_utils.cpp
index fb3506e..784b4d1 100644
--- a/phosphor-regulators/src/error_logging_utils.cpp
+++ b/phosphor-regulators/src/error_logging_utils.cpp
@@ -27,6 +27,8 @@
 
 #include <vector>
 
+using ConfigFileParserError = phosphor::power::util::ConfigFileParserError;
+
 namespace phosphor::power::regulators::error_logging_utils
 {
 
diff --git a/phosphor-regulators/test/config_file_parser_tests.cpp b/phosphor-regulators/test/config_file_parser_tests.cpp
index 92096fb..9a68990 100644
--- a/phosphor-regulators/test/config_file_parser_tests.cpp
+++ b/phosphor-regulators/test/config_file_parser_tests.cpp
@@ -68,6 +68,7 @@
 using namespace phosphor::power::regulators;
 using namespace phosphor::power::regulators::config_file_parser;
 using namespace phosphor::power::regulators::config_file_parser::internal;
+using ConfigFileParserError = phosphor::power::util::ConfigFileParserError;
 using json = nlohmann::json;
 using TemporaryFile = phosphor::power::util::TemporaryFile;
 
@@ -209,28 +210,6 @@
     }
 }
 
-TEST(ConfigFileParserTests, GetRequiredProperty)
-{
-    // Test where property exists
-    {
-        const json element = R"( { "format": "linear" } )"_json;
-        const json& propertyElement = getRequiredProperty(element, "format");
-        EXPECT_EQ(propertyElement.get<std::string>(), "linear");
-    }
-
-    // Test where property does not exist
-    try
-    {
-        const json element = R"( { "volts": 1.03 } )"_json;
-        getRequiredProperty(element, "format");
-        ADD_FAILURE() << "Should not have reached this line.";
-    }
-    catch (const std::invalid_argument& e)
-    {
-        EXPECT_STREQ(e.what(), "Required property missing: format");
-    }
-}
-
 TEST(ConfigFileParserTests, ParseAction)
 {
     // Test where works: comments property specified
@@ -654,141 +633,6 @@
     }
 }
 
-TEST(ConfigFileParserTests, ParseBitPosition)
-{
-    // Test where works: 0
-    {
-        const json element = R"( 0 )"_json;
-        uint8_t value = parseBitPosition(element);
-        EXPECT_EQ(value, 0);
-    }
-
-    // Test where works: 7
-    {
-        const json element = R"( 7 )"_json;
-        uint8_t value = parseBitPosition(element);
-        EXPECT_EQ(value, 7);
-    }
-
-    // Test where fails: Element is not an integer
-    try
-    {
-        const json element = R"( 1.03 )"_json;
-        parseBitPosition(element);
-        ADD_FAILURE() << "Should not have reached this line.";
-    }
-    catch (const std::invalid_argument& e)
-    {
-        EXPECT_STREQ(e.what(), "Element is not an integer");
-    }
-
-    // Test where fails: Value < 0
-    try
-    {
-        const json element = R"( -1 )"_json;
-        parseBitPosition(element);
-        ADD_FAILURE() << "Should not have reached this line.";
-    }
-    catch (const std::invalid_argument& e)
-    {
-        EXPECT_STREQ(e.what(), "Element is not a bit position");
-    }
-
-    // Test where fails: Value > 7
-    try
-    {
-        const json element = R"( 8 )"_json;
-        parseBitPosition(element);
-        ADD_FAILURE() << "Should not have reached this line.";
-    }
-    catch (const std::invalid_argument& e)
-    {
-        EXPECT_STREQ(e.what(), "Element is not a bit position");
-    }
-}
-
-TEST(ConfigFileParserTests, ParseBitValue)
-{
-    // Test where works: 0
-    {
-        const json element = R"( 0 )"_json;
-        uint8_t value = parseBitValue(element);
-        EXPECT_EQ(value, 0);
-    }
-
-    // Test where works: 1
-    {
-        const json element = R"( 1 )"_json;
-        uint8_t value = parseBitValue(element);
-        EXPECT_EQ(value, 1);
-    }
-
-    // Test where fails: Element is not an integer
-    try
-    {
-        const json element = R"( 0.5 )"_json;
-        parseBitValue(element);
-        ADD_FAILURE() << "Should not have reached this line.";
-    }
-    catch (const std::invalid_argument& e)
-    {
-        EXPECT_STREQ(e.what(), "Element is not an integer");
-    }
-
-    // Test where fails: Value < 0
-    try
-    {
-        const json element = R"( -1 )"_json;
-        parseBitValue(element);
-        ADD_FAILURE() << "Should not have reached this line.";
-    }
-    catch (const std::invalid_argument& e)
-    {
-        EXPECT_STREQ(e.what(), "Element is not a bit value");
-    }
-
-    // Test where fails: Value > 1
-    try
-    {
-        const json element = R"( 2 )"_json;
-        parseBitValue(element);
-        ADD_FAILURE() << "Should not have reached this line.";
-    }
-    catch (const std::invalid_argument& e)
-    {
-        EXPECT_STREQ(e.what(), "Element is not a bit value");
-    }
-}
-
-TEST(ConfigFileParserTests, ParseBoolean)
-{
-    // Test where works: true
-    {
-        const json element = R"( true )"_json;
-        bool value = parseBoolean(element);
-        EXPECT_EQ(value, true);
-    }
-
-    // Test where works: false
-    {
-        const json element = R"( false )"_json;
-        bool value = parseBoolean(element);
-        EXPECT_EQ(value, false);
-    }
-
-    // Test where fails: Element is not a boolean
-    try
-    {
-        const json element = R"( 1 )"_json;
-        parseBoolean(element);
-        ADD_FAILURE() << "Should not have reached this line.";
-    }
-    catch (const std::invalid_argument& e)
-    {
-        EXPECT_STREQ(e.what(), "Element is not a boolean");
-    }
-}
-
 TEST(ConfigFileParserTests, ParseChassis)
 {
     // Test where works: Only required properties specified
@@ -1950,166 +1794,6 @@
     }
 }
 
-TEST(ConfigFileParserTests, ParseDouble)
-{
-    // Test where works: floating point value
-    {
-        const json element = R"( 1.03 )"_json;
-        double value = parseDouble(element);
-        EXPECT_EQ(value, 1.03);
-    }
-
-    // Test where works: integer value
-    {
-        const json element = R"( 24 )"_json;
-        double value = parseDouble(element);
-        EXPECT_EQ(value, 24.0);
-    }
-
-    // Test where fails: Element is not a number
-    try
-    {
-        const json element = R"( true )"_json;
-        parseDouble(element);
-        ADD_FAILURE() << "Should not have reached this line.";
-    }
-    catch (const std::invalid_argument& e)
-    {
-        EXPECT_STREQ(e.what(), "Element is not a number");
-    }
-}
-
-TEST(ConfigFileParserTests, ParseHexByte)
-{
-    // Test where works: "0xFF"
-    {
-        const json element = R"( "0xFF" )"_json;
-        uint8_t value = parseHexByte(element);
-        EXPECT_EQ(value, 0xFF);
-    }
-
-    // Test where works: "0xff"
-    {
-        const json element = R"( "0xff" )"_json;
-        uint8_t value = parseHexByte(element);
-        EXPECT_EQ(value, 0xff);
-    }
-
-    // Test where works: "0xf"
-    {
-        const json element = R"( "0xf" )"_json;
-        uint8_t value = parseHexByte(element);
-        EXPECT_EQ(value, 0xf);
-    }
-
-    // Test where fails: "0xfff"
-    try
-    {
-        const json element = R"( "0xfff" )"_json;
-        parseHexByte(element);
-        ADD_FAILURE() << "Should not have reached this line.";
-    }
-    catch (const std::invalid_argument& e)
-    {
-        EXPECT_STREQ(e.what(), "Element is not hexadecimal string");
-    }
-
-    // Test where fails: "0xAG"
-    try
-    {
-        const json element = R"( "0xAG" )"_json;
-        parseHexByte(element);
-        ADD_FAILURE() << "Should not have reached this line.";
-    }
-    catch (const std::invalid_argument& e)
-    {
-        EXPECT_STREQ(e.what(), "Element is not hexadecimal string");
-    }
-
-    // Test where fails: "ff"
-    try
-    {
-        const json element = R"( "ff" )"_json;
-        parseHexByte(element);
-        ADD_FAILURE() << "Should not have reached this line.";
-    }
-    catch (const std::invalid_argument& e)
-    {
-        EXPECT_STREQ(e.what(), "Element is not hexadecimal string");
-    }
-
-    // Test where fails: ""
-    try
-    {
-        const json element = "";
-        parseHexByte(element);
-        ADD_FAILURE() << "Should not have reached this line.";
-    }
-    catch (const std::invalid_argument& e)
-    {
-        EXPECT_STREQ(e.what(), "Element is not hexadecimal string");
-    }
-
-    // Test where fails: "f"
-    try
-    {
-        const json element = R"( "f" )"_json;
-        parseHexByte(element);
-        ADD_FAILURE() << "Should not have reached this line.";
-    }
-    catch (const std::invalid_argument& e)
-    {
-        EXPECT_STREQ(e.what(), "Element is not hexadecimal string");
-    }
-
-    // Test where fails: "0x"
-    try
-    {
-        const json element = R"( "0x" )"_json;
-        parseHexByte(element);
-        ADD_FAILURE() << "Should not have reached this line.";
-    }
-    catch (const std::invalid_argument& e)
-    {
-        EXPECT_STREQ(e.what(), "Element is not hexadecimal string");
-    }
-
-    // Test where fails: "0Xff"
-    try
-    {
-        const json element = R"( "0XFF" )"_json;
-        parseHexByte(element);
-        ADD_FAILURE() << "Should not have reached this line.";
-    }
-    catch (const std::invalid_argument& e)
-    {
-        EXPECT_STREQ(e.what(), "Element is not hexadecimal string");
-    }
-}
-
-TEST(ConfigFileParserTests, ParseHexByteArray)
-{
-    // Test where works
-    {
-        const json element = R"( [ "0xCC", "0xFF" ] )"_json;
-        std::vector<uint8_t> hexBytes = parseHexByteArray(element);
-        std::vector<uint8_t> expected = {0xcc, 0xff};
-        EXPECT_EQ(hexBytes, expected);
-    }
-
-    // Test where fails: Element is not an array
-    try
-    {
-        const json element = 0;
-        parseHexByteArray(element);
-        ADD_FAILURE() << "Should not have reached this line.";
-    }
-    catch (const std::invalid_argument& e)
-    {
-        EXPECT_STREQ(e.what(), "Element is not an array");
-    }
-}
-
 TEST(ConfigFileParserTests, ParseI2CCaptureBytes)
 {
     // Test where works
@@ -3339,59 +3023,6 @@
     }
 }
 
-TEST(ConfigFileParserTests, ParseInt8)
-{
-    // Test where works: INT8_MIN
-    {
-        const json element = R"( -128 )"_json;
-        int8_t value = parseInt8(element);
-        EXPECT_EQ(value, -128);
-    }
-
-    // Test where works: INT8_MAX
-    {
-        const json element = R"( 127 )"_json;
-        int8_t value = parseInt8(element);
-        EXPECT_EQ(value, 127);
-    }
-
-    // Test where fails: Element is not an integer
-    try
-    {
-        const json element = R"( 1.03 )"_json;
-        parseInt8(element);
-        ADD_FAILURE() << "Should not have reached this line.";
-    }
-    catch (const std::invalid_argument& e)
-    {
-        EXPECT_STREQ(e.what(), "Element is not an integer");
-    }
-
-    // Test where fails: Value < INT8_MIN
-    try
-    {
-        const json element = R"( -129 )"_json;
-        parseInt8(element);
-        ADD_FAILURE() << "Should not have reached this line.";
-    }
-    catch (const std::invalid_argument& e)
-    {
-        EXPECT_STREQ(e.what(), "Element is not an 8-bit signed integer");
-    }
-
-    // Test where fails: Value > INT8_MAX
-    try
-    {
-        const json element = R"( 128 )"_json;
-        parseInt8(element);
-        ADD_FAILURE() << "Should not have reached this line.";
-    }
-    catch (const std::invalid_argument& e)
-    {
-        EXPECT_STREQ(e.what(), "Element is not an 8-bit signed integer");
-    }
-}
-
 TEST(ConfigFileParserTests, ParseInventoryPath)
 {
     // Test where works: Inventory path has a leading '/'
@@ -5180,134 +4811,6 @@
     }
 }
 
-TEST(ConfigFileParserTests, ParseString)
-{
-    // Test where works: Empty string
-    {
-        const json element = "";
-        std::string value = parseString(element, true);
-        EXPECT_EQ(value, "");
-    }
-
-    // Test where works: Non-empty string
-    {
-        const json element = "vdd_regulator";
-        std::string value = parseString(element, false);
-        EXPECT_EQ(value, "vdd_regulator");
-    }
-
-    // Test where fails: Element is not a string
-    try
-    {
-        const json element = R"( { "foo": "bar" } )"_json;
-        parseString(element);
-        ADD_FAILURE() << "Should not have reached this line.";
-    }
-    catch (const std::invalid_argument& e)
-    {
-        EXPECT_STREQ(e.what(), "Element is not a string");
-    }
-
-    // Test where fails: Empty string
-    try
-    {
-        const json element = "";
-        parseString(element);
-        ADD_FAILURE() << "Should not have reached this line.";
-    }
-    catch (const std::invalid_argument& e)
-    {
-        EXPECT_STREQ(e.what(), "Element contains an empty string");
-    }
-}
-
-TEST(ConfigFileParserTests, ParseUint8)
-{
-    // Test where works: 0
-    {
-        const json element = R"( 0 )"_json;
-        uint8_t value = parseUint8(element);
-        EXPECT_EQ(value, 0);
-    }
-
-    // Test where works: UINT8_MAX
-    {
-        const json element = R"( 255 )"_json;
-        uint8_t value = parseUint8(element);
-        EXPECT_EQ(value, 255);
-    }
-
-    // Test where fails: Element is not an integer
-    try
-    {
-        const json element = R"( 1.03 )"_json;
-        parseUint8(element);
-        ADD_FAILURE() << "Should not have reached this line.";
-    }
-    catch (const std::invalid_argument& e)
-    {
-        EXPECT_STREQ(e.what(), "Element is not an integer");
-    }
-
-    // Test where fails: Value < 0
-    try
-    {
-        const json element = R"( -1 )"_json;
-        parseUint8(element);
-        ADD_FAILURE() << "Should not have reached this line.";
-    }
-    catch (const std::invalid_argument& e)
-    {
-        EXPECT_STREQ(e.what(), "Element is not an 8-bit unsigned integer");
-    }
-
-    // Test where fails: Value > UINT8_MAX
-    try
-    {
-        const json element = R"( 256 )"_json;
-        parseUint8(element);
-        ADD_FAILURE() << "Should not have reached this line.";
-    }
-    catch (const std::invalid_argument& e)
-    {
-        EXPECT_STREQ(e.what(), "Element is not an 8-bit unsigned integer");
-    }
-}
-
-TEST(ConfigFileParserTests, ParseUnsignedInteger)
-{
-    // Test where works: 1
-    {
-        const json element = R"( 1 )"_json;
-        unsigned int value = parseUnsignedInteger(element);
-        EXPECT_EQ(value, 1);
-    }
-
-    // Test where fails: Element is not an integer
-    try
-    {
-        const json element = R"( 1.5 )"_json;
-        parseUnsignedInteger(element);
-        ADD_FAILURE() << "Should not have reached this line.";
-    }
-    catch (const std::invalid_argument& e)
-    {
-        EXPECT_STREQ(e.what(), "Element is not an unsigned integer");
-    }
-
-    // Test where fails: Value < 0
-    try
-    {
-        const json element = R"( -1 )"_json;
-        parseUnsignedInteger(element);
-        ADD_FAILURE() << "Should not have reached this line.";
-    }
-    catch (const std::invalid_argument& e)
-    {
-        EXPECT_STREQ(e.what(), "Element is not an unsigned integer");
-    }
-}
-
 TEST(ConfigFileParserTests, ParseVoutDataFormat)
 {
     // Test where works: linear
@@ -5368,92 +4871,3 @@
         EXPECT_STREQ(e.what(), "Element is not a string");
     }
 }
-
-TEST(ConfigFileParserTests, VerifyIsArray)
-{
-    // Test where element is an array
-    try
-    {
-        const json element = R"( [ "foo", "bar" ] )"_json;
-        verifyIsArray(element);
-    }
-    catch (const std::exception& e)
-    {
-        ADD_FAILURE() << "Should not have caught exception.";
-    }
-
-    // Test where element is not an array
-    try
-    {
-        const json element = R"( { "foo": "bar" } )"_json;
-        verifyIsArray(element);
-        ADD_FAILURE() << "Should not have reached this line.";
-    }
-    catch (const std::invalid_argument& e)
-    {
-        EXPECT_STREQ(e.what(), "Element is not an array");
-    }
-}
-
-TEST(ConfigFileParserTests, VerifyIsObject)
-{
-    // Test where element is an object
-    try
-    {
-        const json element = R"( { "foo": "bar" } )"_json;
-        verifyIsObject(element);
-    }
-    catch (const std::exception& e)
-    {
-        ADD_FAILURE() << "Should not have caught exception.";
-    }
-
-    // Test where element is not an object
-    try
-    {
-        const json element = R"( [ "foo", "bar" ] )"_json;
-        verifyIsObject(element);
-        ADD_FAILURE() << "Should not have reached this line.";
-    }
-    catch (const std::invalid_argument& e)
-    {
-        EXPECT_STREQ(e.what(), "Element is not an object");
-    }
-}
-
-TEST(ConfigFileParserTests, VerifyPropertyCount)
-{
-    // Test where element has expected number of properties
-    try
-    {
-        const json element = R"(
-            {
-              "comments": [ "Set voltage rule" ],
-              "id": "set_voltage_rule"
-            }
-        )"_json;
-        verifyPropertyCount(element, 2);
-    }
-    catch (const std::exception& e)
-    {
-        ADD_FAILURE() << "Should not have caught exception.";
-    }
-
-    // Test where element has unexpected number of properties
-    try
-    {
-        const json element = R"(
-            {
-              "comments": [ "Set voltage rule" ],
-              "id": "set_voltage_rule",
-              "foo": 1.3
-            }
-        )"_json;
-        verifyPropertyCount(element, 2);
-        ADD_FAILURE() << "Should not have reached this line.";
-    }
-    catch (const std::invalid_argument& e)
-    {
-        EXPECT_STREQ(e.what(), "Element contains an invalid property");
-    }
-}
diff --git a/phosphor-regulators/test/error_logging_utils_tests.cpp b/phosphor-regulators/test/error_logging_utils_tests.cpp
index c12a977..1f31e04 100644
--- a/phosphor-regulators/test/error_logging_utils_tests.cpp
+++ b/phosphor-regulators/test/error_logging_utils_tests.cpp
@@ -38,6 +38,7 @@
 #include <gtest/gtest.h>
 
 using namespace phosphor::power::regulators;
+using ConfigFileParserError = phosphor::power::util::ConfigFileParserError;
 
 namespace fs = std::filesystem;
 
diff --git a/phosphor-regulators/test/meson.build b/phosphor-regulators/test/meson.build
index 20fa19d..a43c03e 100644
--- a/phosphor-regulators/test/meson.build
+++ b/phosphor-regulators/test/meson.build
@@ -5,7 +5,6 @@
 
 phosphor_regulators_tests_source_files = [
     'chassis_tests.cpp',
-    'config_file_parser_error_tests.cpp',
     'config_file_parser_tests.cpp',
     'configuration_tests.cpp',
     'device_tests.cpp',
diff --git a/phosphor-regulators/test/config_file_parser_error_tests.cpp b/test/config_file_parser_error_tests.cpp
similarity index 93%
rename from phosphor-regulators/test/config_file_parser_error_tests.cpp
rename to test/config_file_parser_error_tests.cpp
index bb167bd..3833ed7 100644
--- a/phosphor-regulators/test/config_file_parser_error_tests.cpp
+++ b/test/config_file_parser_error_tests.cpp
@@ -1,5 +1,5 @@
 /**
- * Copyright © 2020 IBM Corporation
+ * Copyright © 2025 IBM Corporation
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -19,7 +19,7 @@
 
 #include <gtest/gtest.h>
 
-using namespace phosphor::power::regulators;
+using ConfigFileParserError = phosphor::power::util::ConfigFileParserError;
 
 TEST(ConfigFileParserErrorTests, Constructor)
 {
diff --git a/test/json_parser_utils_tests.cpp b/test/json_parser_utils_tests.cpp
new file mode 100644
index 0000000..e4f591c
--- /dev/null
+++ b/test/json_parser_utils_tests.cpp
@@ -0,0 +1,601 @@
+/**
+ * Copyright © 2025 IBM Corporation
+ *
+ * Licensed 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.
+ */
+#include "json_parser_utils.hpp"
+
+#include <nlohmann/json.hpp>
+
+#include <cstdint>
+#include <exception>
+#include <stdexcept>
+#include <string>
+#include <vector>
+
+#include <gtest/gtest.h>
+
+using namespace phosphor::power::json_parser_utils;
+using json = nlohmann::json;
+
+TEST(JSONParserUtilsTests, GetRequiredProperty)
+{
+    // Test where property exists
+    {
+        const json element = R"( { "format": "linear" } )"_json;
+        const json& propertyElement = getRequiredProperty(element, "format");
+        EXPECT_EQ(propertyElement.get<std::string>(), "linear");
+    }
+
+    // Test where property does not exist
+    try
+    {
+        const json element = R"( { "volts": 1.03 } )"_json;
+        getRequiredProperty(element, "format");
+        ADD_FAILURE() << "Should not have reached this line.";
+    }
+    catch (const std::invalid_argument& e)
+    {
+        EXPECT_STREQ(e.what(), "Required property missing: format");
+    }
+}
+
+TEST(JSONParserUtilsTests, ParseBitPosition)
+{
+    // Test where works: 0
+    {
+        const json element = R"( 0 )"_json;
+        uint8_t value = parseBitPosition(element);
+        EXPECT_EQ(value, 0);
+    }
+
+    // Test where works: 7
+    {
+        const json element = R"( 7 )"_json;
+        uint8_t value = parseBitPosition(element);
+        EXPECT_EQ(value, 7);
+    }
+
+    // Test where fails: Element is not an integer
+    try
+    {
+        const json element = R"( 1.03 )"_json;
+        parseBitPosition(element);
+        ADD_FAILURE() << "Should not have reached this line.";
+    }
+    catch (const std::invalid_argument& e)
+    {
+        EXPECT_STREQ(e.what(), "Element is not an integer");
+    }
+
+    // Test where fails: Value < 0
+    try
+    {
+        const json element = R"( -1 )"_json;
+        parseBitPosition(element);
+        ADD_FAILURE() << "Should not have reached this line.";
+    }
+    catch (const std::invalid_argument& e)
+    {
+        EXPECT_STREQ(e.what(), "Element is not a bit position");
+    }
+
+    // Test where fails: Value > 7
+    try
+    {
+        const json element = R"( 8 )"_json;
+        parseBitPosition(element);
+        ADD_FAILURE() << "Should not have reached this line.";
+    }
+    catch (const std::invalid_argument& e)
+    {
+        EXPECT_STREQ(e.what(), "Element is not a bit position");
+    }
+}
+
+TEST(JSONParserUtilsTests, ParseBitValue)
+{
+    // Test where works: 0
+    {
+        const json element = R"( 0 )"_json;
+        uint8_t value = parseBitValue(element);
+        EXPECT_EQ(value, 0);
+    }
+
+    // Test where works: 1
+    {
+        const json element = R"( 1 )"_json;
+        uint8_t value = parseBitValue(element);
+        EXPECT_EQ(value, 1);
+    }
+
+    // Test where fails: Element is not an integer
+    try
+    {
+        const json element = R"( 0.5 )"_json;
+        parseBitValue(element);
+        ADD_FAILURE() << "Should not have reached this line.";
+    }
+    catch (const std::invalid_argument& e)
+    {
+        EXPECT_STREQ(e.what(), "Element is not an integer");
+    }
+
+    // Test where fails: Value < 0
+    try
+    {
+        const json element = R"( -1 )"_json;
+        parseBitValue(element);
+        ADD_FAILURE() << "Should not have reached this line.";
+    }
+    catch (const std::invalid_argument& e)
+    {
+        EXPECT_STREQ(e.what(), "Element is not a bit value");
+    }
+
+    // Test where fails: Value > 1
+    try
+    {
+        const json element = R"( 2 )"_json;
+        parseBitValue(element);
+        ADD_FAILURE() << "Should not have reached this line.";
+    }
+    catch (const std::invalid_argument& e)
+    {
+        EXPECT_STREQ(e.what(), "Element is not a bit value");
+    }
+}
+
+TEST(JSONParserUtilsTests, ParseBoolean)
+{
+    // Test where works: true
+    {
+        const json element = R"( true )"_json;
+        bool value = parseBoolean(element);
+        EXPECT_EQ(value, true);
+    }
+
+    // Test where works: false
+    {
+        const json element = R"( false )"_json;
+        bool value = parseBoolean(element);
+        EXPECT_EQ(value, false);
+    }
+
+    // Test where fails: Element is not a boolean
+    try
+    {
+        const json element = R"( 1 )"_json;
+        parseBoolean(element);
+        ADD_FAILURE() << "Should not have reached this line.";
+    }
+    catch (const std::invalid_argument& e)
+    {
+        EXPECT_STREQ(e.what(), "Element is not a boolean");
+    }
+}
+
+TEST(JSONParserUtilsTests, ParseDouble)
+{
+    // Test where works: floating point value
+    {
+        const json element = R"( 1.03 )"_json;
+        double value = parseDouble(element);
+        EXPECT_EQ(value, 1.03);
+    }
+
+    // Test where works: integer value
+    {
+        const json element = R"( 24 )"_json;
+        double value = parseDouble(element);
+        EXPECT_EQ(value, 24.0);
+    }
+
+    // Test where fails: Element is not a number
+    try
+    {
+        const json element = R"( true )"_json;
+        parseDouble(element);
+        ADD_FAILURE() << "Should not have reached this line.";
+    }
+    catch (const std::invalid_argument& e)
+    {
+        EXPECT_STREQ(e.what(), "Element is not a number");
+    }
+}
+
+TEST(JSONParserUtilsTests, ParseHexByte)
+{
+    // Test where works: "0xFF"
+    {
+        const json element = R"( "0xFF" )"_json;
+        uint8_t value = parseHexByte(element);
+        EXPECT_EQ(value, 0xFF);
+    }
+
+    // Test where works: "0xff"
+    {
+        const json element = R"( "0xff" )"_json;
+        uint8_t value = parseHexByte(element);
+        EXPECT_EQ(value, 0xff);
+    }
+
+    // Test where works: "0xf"
+    {
+        const json element = R"( "0xf" )"_json;
+        uint8_t value = parseHexByte(element);
+        EXPECT_EQ(value, 0xf);
+    }
+
+    // Test where fails: "0xfff"
+    try
+    {
+        const json element = R"( "0xfff" )"_json;
+        parseHexByte(element);
+        ADD_FAILURE() << "Should not have reached this line.";
+    }
+    catch (const std::invalid_argument& e)
+    {
+        EXPECT_STREQ(e.what(), "Element is not hexadecimal string");
+    }
+
+    // Test where fails: "0xAG"
+    try
+    {
+        const json element = R"( "0xAG" )"_json;
+        parseHexByte(element);
+        ADD_FAILURE() << "Should not have reached this line.";
+    }
+    catch (const std::invalid_argument& e)
+    {
+        EXPECT_STREQ(e.what(), "Element is not hexadecimal string");
+    }
+
+    // Test where fails: "ff"
+    try
+    {
+        const json element = R"( "ff" )"_json;
+        parseHexByte(element);
+        ADD_FAILURE() << "Should not have reached this line.";
+    }
+    catch (const std::invalid_argument& e)
+    {
+        EXPECT_STREQ(e.what(), "Element is not hexadecimal string");
+    }
+
+    // Test where fails: ""
+    try
+    {
+        const json element = "";
+        parseHexByte(element);
+        ADD_FAILURE() << "Should not have reached this line.";
+    }
+    catch (const std::invalid_argument& e)
+    {
+        EXPECT_STREQ(e.what(), "Element is not hexadecimal string");
+    }
+
+    // Test where fails: "f"
+    try
+    {
+        const json element = R"( "f" )"_json;
+        parseHexByte(element);
+        ADD_FAILURE() << "Should not have reached this line.";
+    }
+    catch (const std::invalid_argument& e)
+    {
+        EXPECT_STREQ(e.what(), "Element is not hexadecimal string");
+    }
+
+    // Test where fails: "0x"
+    try
+    {
+        const json element = R"( "0x" )"_json;
+        parseHexByte(element);
+        ADD_FAILURE() << "Should not have reached this line.";
+    }
+    catch (const std::invalid_argument& e)
+    {
+        EXPECT_STREQ(e.what(), "Element is not hexadecimal string");
+    }
+
+    // Test where fails: "0Xff"
+    try
+    {
+        const json element = R"( "0XFF" )"_json;
+        parseHexByte(element);
+        ADD_FAILURE() << "Should not have reached this line.";
+    }
+    catch (const std::invalid_argument& e)
+    {
+        EXPECT_STREQ(e.what(), "Element is not hexadecimal string");
+    }
+}
+
+TEST(JSONParserUtilsTests, ParseHexByteArray)
+{
+    // Test where works
+    {
+        const json element = R"( [ "0xCC", "0xFF" ] )"_json;
+        std::vector<uint8_t> hexBytes = parseHexByteArray(element);
+        std::vector<uint8_t> expected = {0xcc, 0xff};
+        EXPECT_EQ(hexBytes, expected);
+    }
+
+    // Test where fails: Element is not an array
+    try
+    {
+        const json element = 0;
+        parseHexByteArray(element);
+        ADD_FAILURE() << "Should not have reached this line.";
+    }
+    catch (const std::invalid_argument& e)
+    {
+        EXPECT_STREQ(e.what(), "Element is not an array");
+    }
+}
+
+TEST(JSONParserUtilsTests, ParseInt8)
+{
+    // Test where works: INT8_MIN
+    {
+        const json element = R"( -128 )"_json;
+        int8_t value = parseInt8(element);
+        EXPECT_EQ(value, -128);
+    }
+
+    // Test where works: INT8_MAX
+    {
+        const json element = R"( 127 )"_json;
+        int8_t value = parseInt8(element);
+        EXPECT_EQ(value, 127);
+    }
+
+    // Test where fails: Element is not an integer
+    try
+    {
+        const json element = R"( 1.03 )"_json;
+        parseInt8(element);
+        ADD_FAILURE() << "Should not have reached this line.";
+    }
+    catch (const std::invalid_argument& e)
+    {
+        EXPECT_STREQ(e.what(), "Element is not an integer");
+    }
+
+    // Test where fails: Value < INT8_MIN
+    try
+    {
+        const json element = R"( -129 )"_json;
+        parseInt8(element);
+        ADD_FAILURE() << "Should not have reached this line.";
+    }
+    catch (const std::invalid_argument& e)
+    {
+        EXPECT_STREQ(e.what(), "Element is not an 8-bit signed integer");
+    }
+
+    // Test where fails: Value > INT8_MAX
+    try
+    {
+        const json element = R"( 128 )"_json;
+        parseInt8(element);
+        ADD_FAILURE() << "Should not have reached this line.";
+    }
+    catch (const std::invalid_argument& e)
+    {
+        EXPECT_STREQ(e.what(), "Element is not an 8-bit signed integer");
+    }
+}
+
+TEST(JSONParserUtilsTests, ParseString)
+{
+    // Test where works: Empty string
+    {
+        const json element = "";
+        std::string value = parseString(element, true);
+        EXPECT_EQ(value, "");
+    }
+
+    // Test where works: Non-empty string
+    {
+        const json element = "vdd_regulator";
+        std::string value = parseString(element, false);
+        EXPECT_EQ(value, "vdd_regulator");
+    }
+
+    // Test where fails: Element is not a string
+    try
+    {
+        const json element = R"( { "foo": "bar" } )"_json;
+        parseString(element);
+        ADD_FAILURE() << "Should not have reached this line.";
+    }
+    catch (const std::invalid_argument& e)
+    {
+        EXPECT_STREQ(e.what(), "Element is not a string");
+    }
+
+    // Test where fails: Empty string
+    try
+    {
+        const json element = "";
+        parseString(element);
+        ADD_FAILURE() << "Should not have reached this line.";
+    }
+    catch (const std::invalid_argument& e)
+    {
+        EXPECT_STREQ(e.what(), "Element contains an empty string");
+    }
+}
+
+TEST(JSONParserUtilsTests, ParseUint8)
+{
+    // Test where works: 0
+    {
+        const json element = R"( 0 )"_json;
+        uint8_t value = parseUint8(element);
+        EXPECT_EQ(value, 0);
+    }
+
+    // Test where works: UINT8_MAX
+    {
+        const json element = R"( 255 )"_json;
+        uint8_t value = parseUint8(element);
+        EXPECT_EQ(value, 255);
+    }
+
+    // Test where fails: Element is not an integer
+    try
+    {
+        const json element = R"( 1.03 )"_json;
+        parseUint8(element);
+        ADD_FAILURE() << "Should not have reached this line.";
+    }
+    catch (const std::invalid_argument& e)
+    {
+        EXPECT_STREQ(e.what(), "Element is not an integer");
+    }
+
+    // Test where fails: Value < 0
+    try
+    {
+        const json element = R"( -1 )"_json;
+        parseUint8(element);
+        ADD_FAILURE() << "Should not have reached this line.";
+    }
+    catch (const std::invalid_argument& e)
+    {
+        EXPECT_STREQ(e.what(), "Element is not an 8-bit unsigned integer");
+    }
+
+    // Test where fails: Value > UINT8_MAX
+    try
+    {
+        const json element = R"( 256 )"_json;
+        parseUint8(element);
+        ADD_FAILURE() << "Should not have reached this line.";
+    }
+    catch (const std::invalid_argument& e)
+    {
+        EXPECT_STREQ(e.what(), "Element is not an 8-bit unsigned integer");
+    }
+}
+
+TEST(JSONParserUtilsTests, ParseUnsignedInteger)
+{
+    // Test where works: 1
+    {
+        const json element = R"( 1 )"_json;
+        unsigned int value = parseUnsignedInteger(element);
+        EXPECT_EQ(value, 1);
+    }
+
+    // Test where fails: Element is not an integer
+    try
+    {
+        const json element = R"( 1.5 )"_json;
+        parseUnsignedInteger(element);
+        ADD_FAILURE() << "Should not have reached this line.";
+    }
+    catch (const std::invalid_argument& e)
+    {
+        EXPECT_STREQ(e.what(), "Element is not an unsigned integer");
+    }
+
+    // Test where fails: Value < 0
+    try
+    {
+        const json element = R"( -1 )"_json;
+        parseUnsignedInteger(element);
+        ADD_FAILURE() << "Should not have reached this line.";
+    }
+    catch (const std::invalid_argument& e)
+    {
+        EXPECT_STREQ(e.what(), "Element is not an unsigned integer");
+    }
+}
+
+TEST(JSONParserUtilsTests, VerifyIsArray)
+{
+    // Test where element is an array
+    {
+        const json element = R"( [ "foo", "bar" ] )"_json;
+        verifyIsArray(element);
+    }
+
+    // Test where element is not an array
+    try
+    {
+        const json element = R"( { "foo": "bar" } )"_json;
+        verifyIsArray(element);
+        ADD_FAILURE() << "Should not have reached this line.";
+    }
+    catch (const std::invalid_argument& e)
+    {
+        EXPECT_STREQ(e.what(), "Element is not an array");
+    }
+}
+
+TEST(JSONParserUtilsTests, VerifyIsObject)
+{
+    // Test where element is an object
+    {
+        const json element = R"( { "foo": "bar" } )"_json;
+        verifyIsObject(element);
+    }
+
+    // Test where element is not an object
+    try
+    {
+        const json element = R"( [ "foo", "bar" ] )"_json;
+        verifyIsObject(element);
+        ADD_FAILURE() << "Should not have reached this line.";
+    }
+    catch (const std::invalid_argument& e)
+    {
+        EXPECT_STREQ(e.what(), "Element is not an object");
+    }
+}
+
+TEST(JSONParserUtilsTests, VerifyPropertyCount)
+{
+    // Test where element has expected number of properties
+    {
+        const json element = R"(
+            {
+              "comments": [ "Set voltage rule" ],
+              "id": "set_voltage_rule"
+            }
+        )"_json;
+        verifyPropertyCount(element, 2);
+    }
+
+    // Test where element has unexpected number of properties
+    try
+    {
+        const json element = R"(
+            {
+              "comments": [ "Set voltage rule" ],
+              "id": "set_voltage_rule",
+              "foo": 1.3
+            }
+        )"_json;
+        verifyPropertyCount(element, 2);
+        ADD_FAILURE() << "Should not have reached this line.";
+    }
+    catch (const std::invalid_argument& e)
+    {
+        EXPECT_STREQ(e.what(), "Element contains an invalid property");
+    }
+}
diff --git a/test/meson.build b/test/meson.build
index 35f851e..4e096e1 100644
--- a/test/meson.build
+++ b/test/meson.build
@@ -2,8 +2,10 @@
     'common-code-tests',
     executable(
         'common-code-tests',
+        'config_file_parser_error_tests.cpp',
         'file_descriptor_tests.cpp',
         'format_utils_tests.cpp',
+        'json_parser_utils_tests.cpp',
         'nvtest.cpp',
         'temporary_file_tests.cpp',
         'temporary_subdirectory_tests.cpp',