pentaho/pentaho-kettle · error · JsonInputException
JsonInput.Error.BadStructure
JsonInput.Error.BadStructure
Error message
JsonInput.Error.BadStructure
What it means
JsonInputException with message key JsonInput.Error.BadStructure thrown by FastJsonReader.evalCombinedResult when two JSONPath expressions return result lists of different sizes, meaning the JSON structure is inconsistent with the configured parallel field paths. The message includes the mismatched sizes and the two offending paths.
Solutions
- Align all configured field paths so each wildcard path matches the same array: use a common root like $.items[*].name and $.items[*].id.
- Inspect the source JSON and confirm all mapped arrays have the same length at the same nesting level.
- Enable 'Ignore missing path' only if genuinely optional — it does not fix size mismatches, only missing/null paths.
- If the source schema changed, update the paths or normalize the JSON upstream with a transformation.
- Add a JSONPath assertion pre-check (e.g. with Jayway JsonPath) to verify equal result sizes before running the step.
Example fix
// before: mismatched paths $.items[*].name (10 results) $.details[*].id (7 results) // after: aligned under one array $.items[*].name $.items[*].id
Defensive patterns
Strategy: validation
Validate before calling
// pre-check that all mapped JSONPath arrays have equal size
Configuration cfg = Configuration.defaultConfiguration();
DocumentContext ctx = JsonPath.parse(json);
int size = -1;
for (String p : paths) {
int n = ctx.read(p, new TypeRef<List<Object>>(){}).size();
if (size > 0 && n != size) throw new IllegalStateException("Path " + p + " size " + n + " != " + size);
size = n;
} Try / catch
try { runTransformation(); }
catch (KettleException e) {
if (e.getMessage() != null && e.getMessage().contains("BadStructure")) {
// log the two offending paths from the message and realign field config
} else throw e;
} Prevention
- Always derive all field paths from one common wildcard root ($.items[*].x, $.items[*].y).
- Pin and review API schemas — version drift changes array shapes.
- Snapshot a sample response and validate paths with Jayway JsonPath in tests.
- Use consistent wildcard depth; avoid mixing $..x recursive descent with indexed paths.
When it happens
Trigger: evalCombinedResult: for JsonPath path i, result.size() != lastSize while lastSize > 0 and result non-empty — i.e. field paths expected to yield parallel arrays (e.g. $.a[*] with 10 matches vs $.b[*] with 7 matches) within one input object.
Common situations: Configuring several wildcard paths ($.items[*].name, $.items[*].id) against JSON where one array is shorter or nested differently; schema drift from an API version change; optional arrays missing elements; wrong wildcard depth ($..id vs $.items[*].id).
Related errors
- All input files need to have the same number of fields. File
- Append.Exception.InvalidLayoutDetected
- Could not find field in stream
- ElasticSearchBulk.Error.NoJsonFieldFormat
- GetFileNames.Exception.CouldnotFindField
AI-assisted analysis of pentaho/pentaho-kettle@f3058517a1 (2026-09-13).
Data as JSON: /api/errors/db670220981454ff.
Report an issue: GitHub.
Appendix: source
Thrown at plugins/json/core/src/main/java/org/pentaho/di/trans/steps/jsoninput/reader/FastJsonReader.java:245
// built at ctor
return true;
}
@Override
public void clear() {
results.clear();
}
}
private List<List<?>> evalCombinedResult() throws JsonInputException {
int lastSize = -1;
String prevPath = null;
List<List<?>> results = new ArrayList<>( compiledJsonPaths.length );
int i = 0;
for ( JsonPath path : compiledJsonPaths ) {
List<Object> result = getReadContext().read( path );
if ( result.size() != lastSize && lastSize > 0 && !result.isEmpty() ) {
throw new JsonInputException( BaseMessages.getString(
PKG, "JsonInput.Error.BadStructure", result.size(), inputFields[ i ].getPath(), prevPath, lastSize ) );
}
if ( !isIgnoreMissingPath() && ( isAllNull( result ) || result.isEmpty() ) ) {
throw new JsonInputException(
BaseMessages.getString( PKG, "JsonReader.Error.CanNotFindPath", inputFields[ i ].getPath() ) );
}
results.add( result );
lastSize = result.size();
prevPath = inputFields[ i ].getPath();
i++;
}
return results;
}
public static boolean isAllNull( Iterable<?> list ) {
for ( Object obj : list ) {
if ( obj != null ) {
return false;View on GitHub (pinned to f3058517a1)