Repository: apache/hadoop
File: hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/diskbalancer/planner/NodePlan.java (Java)

Code before:
    checkNodes(tree);
    return READER.readValue(tree);
  }

  /**
   * Iterate through the tree structure beginning at the input `node`. This includes
   * checking arrays and within JSON object structures (allowing for nested structures)
   *
   * @param node a node representing the root of tree structure
   * @throws IOException if any unexpected `@class` values are found - this is the
   * pre-existing exception type exposed by the calling code
   */
  private static void checkNodes(JsonNode node) throws IOException {
    if (node == null) {
      return;
    }

    // Check Node and Recurse into child nodes
    if (node.isObject()) {
      Iterator<Map.Entry<String, JsonNode>> fieldsIterator = node.fields();
      while (fieldsIterator.hasNext()) {
        Map.Entry<String, JsonNode> entry = fieldsIterator.next();
        if ("@class".equals(entry.getKey())) {
          String textValue = entry.getValue().asText();
          if (textValue != null && !textValue.isBlank() &&
                  !textValue.startsWith("org.apache.hadoop.hdfs.server")) {
            throw new IOException("Invalid @class value in NodePlan JSON: " + textValue);
          }
        }
        checkNodes(entry.getValue());
      }
    } else if (node.isArray()) {
      for (int i = 0; i < node.size(); i++) {
        checkNodes(node.get(i));
      }
    }
  }

  /**
   * Returns a JSON representation of NodePlan.
   *
   * @return - JSON String
   * @throws IOException
   */
  public String toJson() throws IOException {
    return WRITER.writeValueAsString(this);
  }

  /**
   * gets the Node UUID.
   *

Review comment (suggestion):
don't want to complicate things, but such a restriction should
- at least be documented in `interface Step`'s javadocs, or
- (not prefer, might be overkill for this case) make it configurable

Code after:
   * Parses a JSON string and converts to NodePlan.
   *
   * @param json - JSON String
   * @return NodePlan
   * @throws IOException
   */
  public static NodePlan parseJson(String json) throws IOException {
    JsonNode tree = READER.readTree(json);
    checkNodes(tree);
    return READER.readValue(tree);
  }

  /**
   * Iterate through the tree structure beginning at the input `node`. This includes
   * checking arrays and within JSON object structures (allowing for nested structures)
   *
   * @param node a node representing the root of tree structure
   * @throws IOException if any unexpected `@class` values are found - this is the
   * pre-existing exception type exposed by the calling code
   */
  private static void checkNodes(JsonNode node) throws IOException {
    if (node == null) {
      return;
    }

    // Check Node and Recurse into child nodes
    if (node.isObject()) {
      Iterator<Map.Entry<String, JsonNode>> fieldsIterator = node.fields();
      while (fieldsIterator.hasNext()) {
        Map.Entry<String, JsonNode> entry = fieldsIterator.next();
        if ("@class".equals(entry.getKey())) {
          String textValue = entry.getValue().asText();
          if (textValue != null && !textValue.isBlank() && !stepClassIsAllowed(textValue)) {
            throw new IOException("Invalid @class value in NodePlan JSON: " + textValue);
          }
        }
        checkNodes(entry.getValue());
      }
    } else if (node.isArray()) {
      for (int i = 0; i < node.size(); i++) {
        checkNodes(node.get(i));
      }
    }
  }

  /**
   * Returns a JSON representation of NodePlan.
   *
   * @return - JSON String
   * @throws IOException
   */