Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Problems with top level oneOf #123

Open
ghost opened this issue Jun 18, 2021 · 2 comments
Open

Problems with top level oneOf #123

ghost opened this issue Jun 18, 2021 · 2 comments

Comments

@ghost
Copy link

ghost commented Jun 18, 2021

I have the following schema:

	public static function setUpProperties($properties, Schema $ownerSchema)
	{
		$ownerSchema->oneOf = [
			Object1::schema(),
			Object2::schema(),
			Object3::schema()
		];

		$ownerSchema->setFromRef(self::className());
		$ownerSchema->additionalProperties = false;
	}

where Object1..3 extend from a base object and have a discriminator property that changes a type specific section according to its value. Currently I have the problem that despite the oneOf validation and import working correctly afterwards processObject is called in https://github.com/swaggest/php-json-schema/blob/master/src/Schema.php#L1204-L1206. Then the validator complains about additional properties because I set those to false. If I remove that then I of course get a object back which consists only of additional properties.

My question is now if the thing I try to do here is valid (in JSON Schema) and whether I'm doing something wrong. Or if the additional invokation of processObject should be only done as fallback if nothing else matched before.

@vearutop
Copy link
Member

vearutop commented Jun 20, 2021

Hi, additionalProperties ignore properties defined in sub schemas (like in oneOf/...). If you want to disallow additionalProperties, I'd suggest to move it into every oneOf sub schema.

Another solution could be to name properties from all sub schemas in root schema.

{
 "oneOf": [
   {"properties": {"a": {"type":"string", "b": {"type":"integer"}}}, "required": ["a","b"]},
   {"properties": {"c": {"type":"string", "d": {"type":"integer"}}}, "required": ["c","d"]},
   {"properties": {"e": {"type":"string", "f": {"type":"integer"}}}, "required": ["e","f"]}
 ],
 "properties": {"a":{},"b":{},"c":{},"d":{},"e":{},"f":{}},
 "additionalProperties": false
}

Alternatively, latest JSON schema draft has a concept of unevaluatedProperties, but unfortunately this keyword is not yet supported by swaggest/json-schema.

@ghost
Copy link
Author

ghost commented Jun 28, 2021

Thanks, I was absent hence the late reply. Either setting additionalProperties to true or redefining properties on the parent class would solve the problem that the validator complains about additional properties. However, this unfortunately does not solve my problem because I'm relying on the type of the returned object. Since the parent is oneOf I would expect that the result would be either Object1, Object2 or Object3. With the approaches to circumvent the additionalProperties problem I get the parent class type back.

Again I'm not sure if I'm trying something which is not right or not supported. I'm attaching a minimal example which maybe helps to see better what I want to achieve.

<?php
require_once __DIR__ . '/vendor/autoload.php';

use Swaggest\JsonSchema\Structure\ClassStructure;
use Swaggest\JsonSchema\Schema;

class ParentObject extends ClassStructure
{
    public static function setUpProperties($properties, Schema $ownerSchema)
    {
        $ownerSchema->oneOf = [
            ChildObject1::schema(),
            ChildObject2::schema()
        ];

        $ownerSchema->setFromRef(self::className());
        // remove comment to receive Swaggest\JsonSchema\Exception\ObjectException: Additional properties not allowed
        // $ownerSchema->additionalProperties = false;
    }
}

class BaseObject extends ClassStructure
{
    public $baseAttr1;
    public $baseAttr2;

    public static function setUpProperties($properties, Schema $ownerSchema)
    {
        $properties->baseAttr1 = Schema::string();
        $properties->baseAttr2 = Schema::integer();

        $ownerSchema->type = 'object';
        $ownerSchema->setFromRef(self::className());
        $ownerSchema->required = [
            self::names()->baseAttr1
        ];
        $ownerSchema->additionalProperties = false;
    }
}

class ChildObject1 extends BaseObject
{
    public $typeSpecific;

    public static function setUpProperties($properties, Schema $ownerSchema)
    {
        parent::setUpProperties($properties, $ownerSchema);
        $properties->typeSpecific = ChildObject1TypeSpecific::schema();
        $ownerSchema->required[] = self::names()->typeSpecific;
    }
}

class ChildObject1TypeSpecific extends ClassStructure
{
    public $child1Attr;

    public static function setUpProperties($properties, Schema $ownerSchema)
    {
        $properties->child1Attr = Schema::integer();

        $ownerSchema->type = 'object';
        $ownerSchema->setFromRef(self::className());
        $ownerSchema->required = [
            self::names()->child1Attr
        ];
        $ownerSchema->additionalProperties = true;
    }
}

class ChildObject2 extends BaseObject
{
    public $typeSpecific;

    public static function setUpProperties($properties, Schema $ownerSchema)
    {
        parent::setUpProperties($properties, $ownerSchema);
        $properties->typeSpecific = ChildObject2TypeSpecific::schema();
        $ownerSchema->required[] = self::names()->typeSpecific;
    }
}

class ChildObject2TypeSpecific extends ClassStructure
{
    public $child2Attr;

    public static function setUpProperties($properties, Schema $ownerSchema)
    {
        $properties->child2Attr = Schema::string();

        $ownerSchema->type = 'object';
        $ownerSchema->setFromRef(self::className());
        $ownerSchema->required = [
            self::names()->child2Attr
        ];
        $ownerSchema->additionalProperties = true;
    }
}


$input = <<<JSON
{
    "baseAttr1": "baseAttr1",
    "baseAttr2": 1,
    "typeSpecific": {
        "child2Attr": "child2Attr"
    }
}
JSON;

$imported = ParentObject::import(json_decode($input));
echo get_class($imported); // = ParentObject but expecting ChildObject2

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

No branches or pull requests

1 participant