Skip to content

[13.x] Stringify properties keys in JsonSchema Serializer#60265

Closed
sulimanbenhalim wants to merge 1 commit into
laravel:13.xfrom
sulimanbenhalim:fix/jsonschema-properties-numeric-keys
Closed

[13.x] Stringify properties keys in JsonSchema Serializer#60265
sulimanbenhalim wants to merge 1 commit into
laravel:13.xfrom
sulimanbenhalim:fix/jsonschema-properties-numeric-keys

Conversation

@sulimanbenhalim
Copy link
Copy Markdown

I am building a small EdTech app where teachers can create multiple choice quizes and an AI grades the student submissions. I use Laravel JsonSchema to describe what a valid answer payload looks like, then I run it through opis/json-schema before sending the answer to the AI grader.

The quiz has answer slots A, B, C, D. In the payload the slots are indexed by position so slot A is "0", slot B is "1", etc. When I tried to publish my first quiz the validator kept saying my schema is invalid and every submisison was getting rejected

Here is what I had:

$slots = [];
for ($i = 0; $i < 4; $i++) {
    $slots[(string) $i] = JsonSchema::boolean()->required();
}

$schema = JsonSchema::object([
    'question_id' => JsonSchema::integer()->required(),
    'selected_slots' => JsonSchema::object($slots)->required(),
])->toArray();

When I encode this to JSON, the selected_slots.properties field comes out as an array, not an object:

"selected_slots": {
  "properties": [
    {"type":"boolean"},
    {"type":"boolean"},
    {"type":"boolean"},
    {"type":"boolean"}
  ],
  "type": "object",
  "required": ["0","1","2","3"]
}

opis throws properties must be an object. The JSON Schema spec (Draft 2020-12, the section about the properties keyword) also says it must be an object, so this output is not valid https://json-schema.org/understanding-json-schema/reference/object#properties

PR #60149 was merged last week and it handles the same coercion issue for the required array. But the same logic was not applied to properties itself. The test in that PR uses keys "1" and "4" which PHP coerces to int 1 and 4. Those are not list shaped so json_encode still emits an object and the bug stays hidden. With keys like "0", "1", "2" the array becomes a php list starting from 0 and json_encode emits an array instead.

Fix

PHP coerces numeric string keys to int and there is no way to keep them as strings in an array. The cleanest way I found to make json_encode emit an object is to cast the array to stdClass, but only when it would otherwise be encoded as a list. For all normal cases (string keys like 'name', 'email') nothing changes and the existing tests pass without modification.

Test

I added a test next to the one from #60149 that asserts the JSON output keeps properties as an object when the keys are "0", "1", "2".

Thanks!

@taylorotwell
Copy link
Copy Markdown
Member

Thanks for your pull request to Laravel!

Unfortunately, I'm going to delay merging this code for now. To preserve our ability to adequately maintain the framework, we need to be very careful regarding the amount of code we include.

If applicable, please consider releasing your code as a package so that the community can still take advantage of your contributions!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants