wol-soft / wol-soft/php-json-schema-model-generator
A multi-type property including object with a composition rejects every object value
Nobody has claimed this yet.
- Dominant language
- PHP
- Stars
- 83
- Forks
- 21
- PR merge metrics
- No merged PRs in 30d
Description
Summary
A property whose declared type is a multi-type list containing object, combined with a
composition (anyOf / allOf / oneOf / if) whose branch resolves to an object, rejects every
object value at runtime — with an error comparing two generated class names:
Invalid class for 'p': requires 'Root_Root_P5d2593f4976038d6db2cbff598e75752', got 'Root_P'
The other declared types still work; only object values are affected. This includes
["object", "null"], the ordinary way to spell a nullable object, which makes the shape easy to hit
by accident.
Pre-existing on master. Found while working on #181.
Reproduction
{
"type": "object",
"properties": {
"p": {
"type": ["object", "null"],
"anyOf": [
{
"type": "object",
"required": ["a"]
}
]
}
}
}
new Root(['p' => ['a' => 1]]);
Invalid value for 'p' declined by composition constraint
Requires to match at least one composition element
- Composition element #1: Failed
* Invalid class for 'p': requires 'Root_Root_P5d2593f…', got 'Root_P'
{"a": 1} satisfies the branch: it is an object and it has a. It must be accepted.
Boundary
Only the parent property's declared type matters. The branch below is the same object-typed branch
in every row:
parent type |
object value |
|---|---|
"object" |
accepted |
["object"] |
rejected |
["object", "string"] |
rejected |
["object", "null"] |
rejected |
| (absent) | accepted |
The branch's own spelling makes no difference — a branch declaring "type": "object" and a branch
declaring "type": ["object", "string"] both fail. Applies to anyOf, allOf and if/then/else
alike.
Note the ["object"] row is a separate defect (#189) rather than part of this one, and is already
fixed on the #181 branch; the two remaining multi-type rows are what this issue is about.
Cause
PropertyFactory::createMultiTypeProperty() builds one sub-property per listed type and routes the
object entry through createObjectProperty(), which generates a nested class and attaches an
instantiation decorator. The value handed to the property is therefore instantiated into the
property's own generated class before the composition validator runs.
The composition branch is processed independently and generates a second class of its own, with an
InstanceOfValidator against it. That validator then sees an instance of the property's class and
fails — the two classes describe the same data but are unrelated types.
With a single "object" type, or with no type at all, only one class is involved and nothing
compares them.
Suggested direction
The branch check needs to compare against the value as the branch understands it, rather than
against whichever class instantiated it first. Options worth evaluating:
- Have the multi-type object sub-property and the composition branch share one generated class where
the branch's constraints permit it. - Reset the branch's input to the raw model data (as
ComposedItem.phptplalready does between
branches) so the branch validates the array rather than an instance. - Skip the branch's
InstanceOfValidatorwhen the enclosing property is multi-type, and rely on the
branch's own structural constraints instead.
Test coverage to add
No existing fixture combines a multi-type property with a composition, which is why this has gone
unnoticed. Worth covering:
["object", "null"]and["object", "string"]parents, each withanyOf,allOfand
if/then/else, asserting that a satisfying object is accepted;- a non-satisfying object still rejected, with the branch's real reason rather than a class
mismatch; - the non-object member of the union still accepted;
- the single-type
"object"parent as the control that must keep working.
Related
Fixed on the #181 branch, in the same area but a different mechanism: the same shape with an
untyped branch ({"required": ["a"]} with no type) also rejected every object value, because
the parent's type was injected into the branch and turned it into a multi-type branch with its own
class. That injection is gone, so untyped branches now work; an explicitly object-typed branch still
does not, which is this issue.
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start in PropertyFactory::createMultiTypeProperty() and inspect how its object sub-property reaches composition validation. Compare that flow with the raw-data reset described for ComposedItem.phptpl, then add coverage for the listed union and composition cases. Done means satisfying objects are accepted, failing objects report the branch reason, non-object union values still work, and the single-type object control remains passing.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- json, php
- Domain
- tooling
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 52/100