@@ -176,7 +176,9 @@ public function normalize(mixed $object, string $format = null, array $context =
176176 $ attributeContext = $ this ->getAttributeNormalizationContext ($ object , $ attribute , $ context );
177177
178178 try {
179- $ attributeValue = $ this ->getAttributeValue ($ object , $ attribute , $ format , $ attributeContext );
179+ $ attributeValue = $ attribute === $ this ->classDiscriminatorResolver ?->getMappingForMappedObject($ object )?->getTypeProperty()
180+ ? $ this ->classDiscriminatorResolver ?->getTypeForMappedObject($ object )
181+ : $ this ->getAttributeValue ($ object , $ attribute , $ format , $ attributeContext );
180182 } catch (UninitializedPropertyException $ e ) {
181183 if ($ context [self ::SKIP_UNINITIALIZED_VALUES ] ?? $ this ->defaultContext [self ::SKIP_UNINITIALIZED_VALUES ] ?? true ) {
182184 continue ;
@@ -244,22 +246,18 @@ protected function getAttributes(object $object, ?string $format, array $context
244246 return $ this ->attributesCache [$ key ];
245247 }
246248
247- $ allowedAttributes = $ this ->getAllowedAttributes ($ object , $ context , true );
248-
249- if (false !== $ allowedAttributes ) {
250- if ($ context ['cache_key ' ]) {
251- $ this ->attributesCache [$ key ] = $ allowedAttributes ;
252- }
253-
254- return $ allowedAttributes ;
255- }
256-
257249 $ attributes = $ this ->extractAttributes ($ object , $ format , $ context );
258250
259251 if ($ mapping = $ this ->classDiscriminatorResolver ?->getMappingForMappedObject($ object )) {
260252 array_unshift ($ attributes , $ mapping ->getTypeProperty ());
261253 }
262254
255+ $ allowedAttributes = $ this ->getAllowedAttributes ($ object , $ context , true );
256+
257+ if (false !== $ allowedAttributes ) {
258+ $ attributes = array_intersect ($ attributes , $ allowedAttributes );
259+ }
260+
263261 if ($ context ['cache_key ' ] && \stdClass::class !== $ class ) {
264262 $ this ->attributesCache [$ key ] = $ attributes ;
265263 }
@@ -340,8 +338,12 @@ public function denormalize(mixed $data, string $type, string $format = null, ar
340338 }
341339
342340 if ($ attributeContext [self ::DEEP_OBJECT_TO_POPULATE ] ?? $ this ->defaultContext [self ::DEEP_OBJECT_TO_POPULATE ] ?? false ) {
341+ $ discriminatorMapping = $ this ->classDiscriminatorResolver ?->getMappingForMappedObject($ object );
342+
343343 try {
344- $ attributeContext [self ::OBJECT_TO_POPULATE ] = $ this ->getAttributeValue ($ object , $ attribute , $ format , $ attributeContext );
344+ $ attributeContext [self ::OBJECT_TO_POPULATE ] = $ attribute === $ discriminatorMapping ?->getTypeProperty()
345+ ? $ discriminatorMapping
346+ : $ this ->getAttributeValue ($ object , $ attribute , $ format , $ attributeContext );
345347 } catch (NoSuchPropertyException ) {
346348 }
347349 }
@@ -405,8 +407,10 @@ private function validateAndDenormalize(array $types, string $currentClass, stri
405407 {
406408 $ expectedTypes = [];
407409 $ isUnionType = \count ($ types ) > 1 ;
410+ $ e = null ;
408411 $ extraAttributesException = null ;
409412 $ missingConstructorArgumentsException = null ;
413+ $ isNullable = false ;
410414 foreach ($ types as $ type ) {
411415 if (null === $ data && $ type ->isNullable ()) {
412416 return null ;
@@ -429,18 +433,22 @@ private function validateAndDenormalize(array $types, string $currentClass, stri
429433 // In XML and CSV all basic datatypes are represented as strings, it is e.g. not possible to determine,
430434 // if a value is meant to be a string, float, int or a boolean value from the serialized representation.
431435 // That's why we have to transform the values, if one of these non-string basic datatypes is expected.
436+ $ builtinType = $ type ->getBuiltinType ();
432437 if (\is_string ($ data ) && (XmlEncoder::FORMAT === $ format || CsvEncoder::FORMAT === $ format )) {
433438 if ('' === $ data ) {
434- if (Type::BUILTIN_TYPE_ARRAY === $ builtinType = $ type -> getBuiltinType () ) {
439+ if (Type::BUILTIN_TYPE_ARRAY === $ builtinType ) {
435440 return [];
436441 }
437442
438- if ($ type -> isNullable () && \in_array ( $ builtinType , [ Type::BUILTIN_TYPE_BOOL , Type:: BUILTIN_TYPE_INT , Type:: BUILTIN_TYPE_FLOAT ], true ) ) {
439- return null ;
443+ if (Type::BUILTIN_TYPE_STRING === $ builtinType ) {
444+ return '' ;
440445 }
446+
447+ // Don't return null yet because Object-types that come first may accept empty-string too
448+ $ isNullable = $ isNullable ?: $ type ->isNullable ();
441449 }
442450
443- switch ($ builtinType ?? $ type -> getBuiltinType () ) {
451+ switch ($ builtinType ) {
444452 case Type::BUILTIN_TYPE_BOOL :
445453 // according to https://www.w3.org/TR/xmlschema-2/#boolean, valid representations are "false", "true", "0" and "1"
446454 if ('false ' === $ data || '0 ' === $ data ) {
@@ -537,24 +545,28 @@ private function validateAndDenormalize(array $types, string $currentClass, stri
537545 return $ data ;
538546 }
539547 } catch (NotNormalizableValueException |InvalidArgumentException $ e ) {
540- if (!$ isUnionType ) {
548+ if (!$ isUnionType && ! $ isNullable ) {
541549 throw $ e ;
542550 }
543551 } catch (ExtraAttributesException $ e ) {
544- if (!$ isUnionType ) {
552+ if (!$ isUnionType && ! $ isNullable ) {
545553 throw $ e ;
546554 }
547555
548556 $ extraAttributesException ??= $ e ;
549557 } catch (MissingConstructorArgumentsException $ e ) {
550- if (!$ isUnionType ) {
558+ if (!$ isUnionType && ! $ isNullable ) {
551559 throw $ e ;
552560 }
553561
554562 $ missingConstructorArgumentsException ??= $ e ;
555563 }
556564 }
557565
566+ if ($ isNullable ) {
567+ return null ;
568+ }
569+
558570 if ($ extraAttributesException ) {
559571 throw $ extraAttributesException ;
560572 }
@@ -563,6 +575,10 @@ private function validateAndDenormalize(array $types, string $currentClass, stri
563575 throw $ missingConstructorArgumentsException ;
564576 }
565577
578+ if (!$ isUnionType && $ e ) {
579+ throw $ e ;
580+ }
581+
566582 if ($ context [self ::DISABLE_TYPE_ENFORCEMENT ] ?? $ this ->defaultContext [self ::DISABLE_TYPE_ENFORCEMENT ] ?? false ) {
567583 return $ data ;
568584 }
@@ -602,7 +618,7 @@ private function getTypes(string $currentClass, string $attribute): ?array
602618 return $ this ->typesCache [$ key ] = $ types ;
603619 }
604620
605- if (null !== $ this -> classDiscriminatorResolver && null !== $ discriminatorMapping = $ this ->classDiscriminatorResolver ->getMappingForClass ($ currentClass )) {
621+ if ($ discriminatorMapping = $ this ->classDiscriminatorResolver ? ->getMappingForClass($ currentClass )) {
606622 if ($ discriminatorMapping ->getTypeProperty () === $ attribute ) {
607623 return $ this ->typesCache [$ key ] = [
608624 new Type (Type::BUILTIN_TYPE_STRING ),
0 commit comments