@@ -187,7 +187,9 @@ public function normalize(mixed $object, string $format = null, array $context =
187187 $ attributeContext = $ this ->getAttributeNormalizationContext ($ object , $ attribute , $ context );
188188
189189 try {
190- $ attributeValue = $ this ->getAttributeValue ($ object , $ attribute , $ format , $ attributeContext );
190+ $ attributeValue = $ attribute === $ this ->classDiscriminatorResolver ?->getMappingForMappedObject($ object )?->getTypeProperty()
191+ ? $ this ->classDiscriminatorResolver ?->getTypeForMappedObject($ object )
192+ : $ this ->getAttributeValue ($ object , $ attribute , $ format , $ attributeContext );
191193 } catch (UninitializedPropertyException $ e ) {
192194 if ($ context [self ::SKIP_UNINITIALIZED_VALUES ] ?? $ this ->defaultContext [self ::SKIP_UNINITIALIZED_VALUES ] ?? true ) {
193195 continue ;
@@ -258,22 +260,18 @@ protected function getAttributes(object $object, ?string $format, array $context
258260 return $ this ->attributesCache [$ key ];
259261 }
260262
261- $ allowedAttributes = $ this ->getAllowedAttributes ($ object , $ context , true );
262-
263- if (false !== $ allowedAttributes ) {
264- if ($ context ['cache_key ' ]) {
265- $ this ->attributesCache [$ key ] = $ allowedAttributes ;
266- }
267-
268- return $ allowedAttributes ;
269- }
270-
271263 $ attributes = $ this ->extractAttributes ($ object , $ format , $ context );
272264
273265 if ($ mapping = $ this ->classDiscriminatorResolver ?->getMappingForMappedObject($ object )) {
274266 array_unshift ($ attributes , $ mapping ->getTypeProperty ());
275267 }
276268
269+ $ allowedAttributes = $ this ->getAllowedAttributes ($ object , $ context , true );
270+
271+ if (false !== $ allowedAttributes ) {
272+ $ attributes = array_intersect ($ attributes , $ allowedAttributes );
273+ }
274+
277275 if ($ context ['cache_key ' ] && \stdClass::class !== $ class ) {
278276 $ this ->attributesCache [$ key ] = $ attributes ;
279277 }
@@ -364,8 +362,12 @@ public function denormalize(mixed $data, string $type, string $format = null, ar
364362 }
365363
366364 if ($ attributeContext [self ::DEEP_OBJECT_TO_POPULATE ] ?? $ this ->defaultContext [self ::DEEP_OBJECT_TO_POPULATE ] ?? false ) {
365+ $ discriminatorMapping = $ this ->classDiscriminatorResolver ?->getMappingForMappedObject($ object );
366+
367367 try {
368- $ attributeContext [self ::OBJECT_TO_POPULATE ] = $ this ->getAttributeValue ($ object , $ attribute , $ format , $ attributeContext );
368+ $ attributeContext [self ::OBJECT_TO_POPULATE ] = $ attribute === $ discriminatorMapping ?->getTypeProperty()
369+ ? $ discriminatorMapping
370+ : $ this ->getAttributeValue ($ object , $ attribute , $ format , $ attributeContext );
369371 } catch (NoSuchPropertyException ) {
370372 }
371373 }
@@ -432,8 +434,10 @@ private function validateAndDenormalize(array $types, string $currentClass, stri
432434 {
433435 $ expectedTypes = [];
434436 $ isUnionType = \count ($ types ) > 1 ;
437+ $ e = null ;
435438 $ extraAttributesException = null ;
436439 $ missingConstructorArgumentsException = null ;
440+ $ isNullable = false ;
437441 foreach ($ types as $ type ) {
438442 if (null === $ data && $ type ->isNullable ()) {
439443 return null ;
@@ -456,18 +460,22 @@ private function validateAndDenormalize(array $types, string $currentClass, stri
456460 // In XML and CSV all basic datatypes are represented as strings, it is e.g. not possible to determine,
457461 // if a value is meant to be a string, float, int or a boolean value from the serialized representation.
458462 // That's why we have to transform the values, if one of these non-string basic datatypes is expected.
463+ $ builtinType = $ type ->getBuiltinType ();
459464 if (\is_string ($ data ) && (XmlEncoder::FORMAT === $ format || CsvEncoder::FORMAT === $ format )) {
460465 if ('' === $ data ) {
461- if (Type::BUILTIN_TYPE_ARRAY === $ builtinType = $ type -> getBuiltinType () ) {
466+ if (Type::BUILTIN_TYPE_ARRAY === $ builtinType ) {
462467 return [];
463468 }
464469
465- if ($ type -> isNullable () && \in_array ( $ builtinType , [ Type::BUILTIN_TYPE_BOOL , Type:: BUILTIN_TYPE_INT , Type:: BUILTIN_TYPE_FLOAT ], true ) ) {
466- return null ;
470+ if (Type::BUILTIN_TYPE_STRING === $ builtinType ) {
471+ return '' ;
467472 }
473+
474+ // Don't return null yet because Object-types that come first may accept empty-string too
475+ $ isNullable = $ isNullable ?: $ type ->isNullable ();
468476 }
469477
470- switch ($ builtinType ?? $ type -> getBuiltinType () ) {
478+ switch ($ builtinType ) {
471479 case Type::BUILTIN_TYPE_BOOL :
472480 // according to https://www.w3.org/TR/xmlschema-2/#boolean, valid representations are "false", "true", "0" and "1"
473481 if ('false ' === $ data || '0 ' === $ data ) {
@@ -564,24 +572,28 @@ private function validateAndDenormalize(array $types, string $currentClass, stri
564572 return $ data ;
565573 }
566574 } catch (NotNormalizableValueException |InvalidArgumentException $ e ) {
567- if (!$ isUnionType ) {
575+ if (!$ isUnionType && ! $ isNullable ) {
568576 throw $ e ;
569577 }
570578 } catch (ExtraAttributesException $ e ) {
571- if (!$ isUnionType ) {
579+ if (!$ isUnionType && ! $ isNullable ) {
572580 throw $ e ;
573581 }
574582
575583 $ extraAttributesException ??= $ e ;
576584 } catch (MissingConstructorArgumentsException $ e ) {
577- if (!$ isUnionType ) {
585+ if (!$ isUnionType && ! $ isNullable ) {
578586 throw $ e ;
579587 }
580588
581589 $ missingConstructorArgumentsException ??= $ e ;
582590 }
583591 }
584592
593+ if ($ isNullable ) {
594+ return null ;
595+ }
596+
585597 if ($ extraAttributesException ) {
586598 throw $ extraAttributesException ;
587599 }
@@ -590,6 +602,10 @@ private function validateAndDenormalize(array $types, string $currentClass, stri
590602 throw $ missingConstructorArgumentsException ;
591603 }
592604
605+ if (!$ isUnionType && $ e ) {
606+ throw $ e ;
607+ }
608+
593609 if ($ context [self ::DISABLE_TYPE_ENFORCEMENT ] ?? $ this ->defaultContext [self ::DISABLE_TYPE_ENFORCEMENT ] ?? false ) {
594610 return $ data ;
595611 }
@@ -629,7 +645,7 @@ private function getTypes(string $currentClass, string $attribute): ?array
629645 return $ this ->typesCache [$ key ] = $ types ;
630646 }
631647
632- if (null !== $ this -> classDiscriminatorResolver && null !== $ discriminatorMapping = $ this ->classDiscriminatorResolver ->getMappingForClass ($ currentClass )) {
648+ if ($ discriminatorMapping = $ this ->classDiscriminatorResolver ? ->getMappingForClass($ currentClass )) {
633649 if ($ discriminatorMapping ->getTypeProperty () === $ attribute ) {
634650 return $ this ->typesCache [$ key ] = [
635651 new Type (Type::BUILTIN_TYPE_STRING ),
0 commit comments