Problem/Motivation

Like commerce's order,It use 'any' type data to store object:Adjustment, But the TypedDataNormalizer just return the Adjustment object, It cause it can't be encode, So it can't be serializad

First(laravel): Let's look at laravel's serialization
laravel's serialization has two steps:

  1. toArray() ====> corresponds to druapl's normalize
  2. toJson() ====> corresponds to drupal's encode

laravel's toArray() is recursive,it will recurs to check whether the child value is Arrayable
https://github.com/illuminate/database/blob/5.5/Eloquent/Model.php#L918
https://github.com/illuminate/database/blob/5.5/Eloquent/Concerns/HasAtt...

Second(symfony):
https://github.com/symfony/serializer/blob/3.2/Serializer.php#L138

    public function normalize($data, $format = null, array $context = array())
    {
        // If a normalizer supports the given data, use it
        if ($normalizer = $this->getNormalizer($data, $format)) {
            return $normalizer->normalize($data, $format, $context);
        }
        if (null === $data || is_scalar($data)) {
            return $data;
        }
        if (is_array($data) || $data instanceof \Traversable) {
            $normalized = array();
            foreach ($data as $key => $val) {
                $normalized[$key] = $this->normalize($val, $format, $context);
            }
            return $normalized;
        }
        if (is_object($data)) {
            if (!$this->normalizers) {
                throw new LogicException('You must register at least one normalizer to be able to normalize objects.');
            }
            throw new UnexpectedValueException(sprintf('Could not normalize object of type %s, no supporting normalizer found.', get_class($data)));
        }
        throw new UnexpectedValueException(sprintf('An unexpected value could not be normalized: %s', var_export($data, true)));
    }

symfony's Serializer->normalize() has the ability to recursive normalize object and array too

Both laravel and symfony has the ability to recursive normalize object or array

Then(Drupal):
Let's see TypedDataNormalizer used by normalizing 'any' type data
core/modules/serialization/src/Normalizer/TypedDataNormalizer.php#n21

  public function normalize($object, $format = NULL, array $context = []) {
    return $object->getValue();
  }

We can see TypedDataNormalizer Just return the value
Drupal use symfony's serializer, But it dosn't follow or take full advantage of the symfony's serializer's design

Proposed resolution

  1. make TypedDataNormalizer flow and use symfony's serialize->normalize()'s design. (solve here)
  2. Map type data has similar trouble too: But Map have other more base problem, It can't be normalized when it has no propertydefinition , But if TypedDataNormalizer's problem is solved, Two kind of https://www.drupal.org/node/2895532's the patch(two kind of controversial solution) will work good with no change needed

Remaining tasks

User interface changes

API changes

Data model changes

CommentFileSizeAuthor
#90 2915705-90.patch20.82 KByevko
#83 2915705-82.patch20.78 KBjurgenhaas
#66 2915705-66.patch21.57 KBsch4lly
#59 interdiff-2915705-59-55.txt2.17 KBdravenk
#59 2915705-59.patch21.18 KBdravenk
#55 interdiff-2915705-52-55.txt1.34 KBlawxen
#55 2915705-55.patch21.77 KBlawxen
#52 interdiff-2915705-49-52.txt4.11 KBlawxen
#52 2915705-52.patch20.97 KBlawxen
#50 interdiff-2915705-43-49.txt17.64 KBlawxen
#49 interdiff-2915705-47-49.txt1.4 KBlawxen
#49 2915705-49.patch20.43 KBlawxen
#47 interdiff-2915705-45-47.txt10.59 KBlawxen
#47 2915705-47.patch19.5 KBlawxen
#45 interdiff-2915705-43-45b.txt12.59 KBlawxen
#45 2915705-45b.patch14.93 KBlawxen
#43 2915705-43.patch13.78 KBwim leers
#39 interdiff-2915705-37-39.txt564 byteslawxen
#39 2915705-39.patch15.02 KBlawxen
#37 interdiff-2915705-36-37.txt2.72 KBlawxen
#37 2915705-37.patch15 KBlawxen
#36 interdiff-2915705-29-36.txt5.21 KBlawxen
#36 2915705-36.patch13.38 KBlawxen
#29 2915705-29-85x.patch7.59 KBlawxen
#29 2915705-29-84x.patch7.53 KBlawxen
#29 interdiff-2915705-26-29-85x.txt1.52 KBlawxen
#29 interdiff-2915705-26-29-84x.txt1.47 KBlawxen
#29 interdiff-2915705-29-84x-85x.txt630 byteslawxen
#26 2915705-26-85x.patch7.57 KBlawxen
#26 2915705-26-84x.patch7.51 KBlawxen
#23 interdiff-2915705-21-23.txt5.24 KBlawxen
#23 2915705-23.patch7.57 KBlawxen
#21 2915705-21.patch6.5 KBwim leers
#21 interdiff.txt2.03 KBwim leers
#19 2915705-19.patch6.39 KBlawxen
#19 interdiff-2915705-16-19.txt10.85 KBlawxen
#16 2915705-16.patch5.31 KBlawxen
#16 interdiff-2915705-14-16.txt2.16 KBlawxen
#16 test-only-16.patch4.45 KBlawxen
#14 2915705-14.patch5.33 KBlawxen
#14 interdiff-2915705-12-14.txt2.64 KBlawxen
#14 test-only-14.patch4.48 KBlawxen
#12 2915705-12.patch5.5 KBlawxen
#12 interdiff-2915705-10-12.txt8.96 KBlawxen
#12 test-only-12.patch4.64 KBlawxen
#10 2915705-10.patch5.53 KBlawxen
#10 interdiff-2915705-2-10.txt5.25 KBlawxen
#10 test-only.patch4.67 KBlawxen
#3 interdiff-2915705-1-2.txt924 byteslawxen
#3 2915705-2.patch886 byteslawxen
#2 2915705-1.patch871 byteslawxen

Issue fork drupal-2915705

Command icon Show commands

Start within a Git clone of the project using the version control instructions.

Or, if you do not have SSH keys set up on git.drupalcode.org:

Comments

liuxin created an issue. See original summary.

lawxen’s picture

StatusFileSize
new871 bytes

The any use TypedDataNormalizer to normalize data
If the getValue return object
add checking:wherher the object could be further normalized

lawxen’s picture

Issue summary: View changes
Status: Active » Needs review
Related issues: +#2916252: [PP-1] Order's Adjustment can't be normalized and serialized
StatusFileSize
new886 bytes
new924 bytes

symfony's Serializer's normalzer()'s design dont's will recursive normalize object and array, and don't allow object which can't be normalizable(didn't implement TypedDataInterface or it's child interface) to pass
So the patch Follow and take full advantage of the symfony's serializer's normalzer()'s design
Then when data which is unnormalizable object or has unnormalizable daughter object stores to a typed data,It must be normalizable
So,I then create a commerce issue about commerce order's adjustment as a demo:https://www.drupal.org/node/2916252

lawxen’s picture

lawxen’s picture

Title: TypedData 'Any' can't be normalized to array if it stores an normalizable object » TypedData 'Any' can't be normalized to array if it stores an object
wim leers’s picture

Issue tags: +API-First Initiative
wim leers’s picture

Component: typed data system » serialization.module
Issue tags: +Needs tests

This looks sensible to me. But this definitely needs (unit) test coverage!

wim leers’s picture

Also, this looks extremely similar to the code in #2871591: Allow ComplexData in TypedData to specify computed properties that should be exposed in normalization and other contexts that @tedbow reverted (in #2871591-142: Allow ComplexData in TypedData to specify computed properties that should be exposed in normalization and other contexts) because it was no longer necessary in that particular issue.

Different, but in the exact same class, doing something similar — I suspect @tedbow is the best candidate to review this in detail :)

tedbow’s picture

Status: Needs review » Needs work

This does reasonable to me but probably not nothing in core is passing that if statement right now so as @Wim Leers says will needs tests.

There should be a "test-only" patch to prove that an array or object won't normalize correctly now.

lawxen’s picture

StatusFileSize
new4.67 KB
new5.25 KB
new5.53 KB
  1. add test-only.patch
  2. 2915705-10.patch :
    1. change comment of TypedDataNormalizer->normalize()

      object value of array's child object must be normalizable.

      to

      object must be typed data or instanceof Traversable.

    2. add test code
tedbow’s picture

@caseylau thanks for providing comprehensive text coverage.

  1. +++ b/core/tests/Drupal/Tests/Core/TypedData/AnyDataNormalizeTest.php
    @@ -0,0 +1,178 @@
    +  protected function buildDataBase() {
    

    I think this function name is confusing. Too close to "build database".

    Maybe buildDataBasicString()

  2. +++ b/core/tests/Drupal/Tests/Core/TypedData/AnyDataNormalizeTest.php
    @@ -0,0 +1,178 @@
    +   * Builds example type data of any, with an object which instanceof Traversable stored.
    

    Nit: comment to long. First sentence should less than 80.

  3. +++ b/core/tests/Drupal/Tests/Core/TypedData/AnyDataNormalizeTest.php
    @@ -0,0 +1,178 @@
    +   * Builds example type data of any with typed data stored.
    

    To clear in this and all the function comments we should surround "any" with single quotes. So 'any'.

  4. +++ b/core/tests/Drupal/Tests/Core/TypedData/AnyDataNormalizeTest.php
    @@ -0,0 +1,178 @@
    +    $this->property3 = "value3";
    

    Maybe I missing something but why is $property3 declared dynamically compared to the other 2 properties.

    If we didn't need to do this we could remove the constructor.

lawxen’s picture

StatusFileSize
new4.64 KB
new8.96 KB
new5.5 KB

@tedbow Thanks for reviewing the code and so many improvable advice.

  1. I corrected the non-standard code.
  2. Rename the AnyDataNormalizeTest.php to AnyTypedDataNormalizeTest.php to be more significative.
skyredwang’s picture

+++ b/core/tests/Drupal/Tests/Core/TypedData/AnyTypedDataNormalizeTest.php
@@ -0,0 +1,184 @@
+   * Tests the normalization of 'any' typed data.
+   *
+   * With an object which instanceof traversable stored.

Don't split one English sentence into two (broken ones). Just make each line only about 80 characters long.

lawxen’s picture

StatusFileSize
new4.48 KB
new2.64 KB
new5.33 KB

Make one English sentence into one line

tstoeckler’s picture

+++ b/core/modules/serialization/src/Normalizer/TypedDataNormalizer.php
@@ -18,7 +18,13 @@ class TypedDataNormalizer extends NormalizerBase {
+    // If the value is object or array, Return the normalized result,
+    // object must be typed data or instanceof Traversable.
+    if (isset($value) && (is_object($value) || is_array($value))) {

So in addition to (or instead of) documenting what $value should be, shouldn't we be checking explicitly with instanceof?

lawxen’s picture

StatusFileSize
new4.45 KB
new2.16 KB
new5.31 KB

change comment instanceof Traversable to traversable

The serializer.php->normalize() has the ability to check whether the object is traversable,

       if (is_array($data) || $data instanceof \Traversable) {
            $normalized = array();
            foreach ($data as $key => $val) {
                $normalized[$key] = $this->normalize($val, $format, $context);
            }
            return $normalized;
        }

If the object is not traversable or typed data, It will give an error reminder to make module make corresponding modification.

If we add check "instanceof \Traversable" in TypedDataNormalizer.php, it will avoid the error reminder,
But it can't check one situation: when the object which isn't traversable appear in an array.
So check "instanceof \Traversable" in TypedDataNormalizer.php is inapposite.

skyredwang’s picture

Status: Needs work » Needs review
wim leers’s picture

Status: Needs review » Needs work

Thanks again for pushing this forward! 👍 This is important, and tricky, so thank you for your patience! 🙏

+++ b/core/modules/serialization/src/Normalizer/TypedDataNormalizer.php
@@ -18,7 +18,13 @@ class TypedDataNormalizer extends NormalizerBase {
   public function normalize($object, $format = NULL, array $context = []) {
...
+    if (isset($value) && (is_object($value) || is_array($value))) {

We're modifying the generic TypedDataNormalizer, which has:

protected $supportedInterfaceOrClass = 'Drupal\Core\TypedData\TypedDataInterface';

We should instead be adding a AnyNormalizer, which has

protected $supportedInterfaceOrClass = '\Drupal\Core\TypedData\Plugin\DataType\Any';

Then we don't need to add those very generic checks to TypedDataNormalizer, and we hugely minimize risk (of applying this logic to other typed data types inappropriately).

lawxen’s picture

Status: Needs work » Needs review
StatusFileSize
new10.85 KB
new6.39 KB

@Wim Leers Thanks for the great advice. I make some change correspond #18

  1. Create AnyNormalizer
  2. add comment about how to make denormalize work
  3. apply the newest drupal8.5.x code change
wim leers’s picture

Status: Needs review » Needs work
  1. +++ b/core/modules/serialization/src/Normalizer/AnyNormalizer.php
    @@ -0,0 +1,34 @@
    +  protected $supportedInterfaceOrClass = 'Drupal\Core\TypedData\Plugin\DataType\Any';
    

    Nit: Let's use Any::class instead of this string.

    (Sorry, I should've said that in my previous review.)

  2. +++ b/core/modules/serialization/src/Normalizer/AnyNormalizer.php
    @@ -0,0 +1,34 @@
    +    // must be normalizable. Tips: 1 Typed data or traversable object are
    +    // normalizable. 2 Field type with 'any' typed data property definition
    

    Tip 1 says it must be either typed data or a traversable object.

    But the if-test only tests is_object(). Shouldn't that then test if ($value instanceof Traversable || $value instanceof TypedDataInterface)?

    Also, why can't we do only $value instanceof Traversable?

  3. +++ b/core/modules/serialization/src/Normalizer/AnyNormalizer.php
    @@ -0,0 +1,34 @@
    +    // which stored traversable object, must convert the array value to object
    +    // in setValue() to make it can be denormalized.
    
    +++ b/core/tests/Drupal/Tests/Core/TypedData/AnyNormalizeTest.php
    @@ -0,0 +1,168 @@
    +  /**
    +   * Builds example 'any' typed data with typed data stored.
    +   */
    +  protected function buildDataWithTypedData() {
    

    I don't understand this "must convert the array value to object in setValue() to make it can be denormalized" at all. Can you point to an example of that?

    What's a real world use case for this?

    You added this in #10. Is it really necessary?

  4. +++ b/core/tests/Drupal/Tests/Core/TypedData/AnyNormalizeTest.php
    @@ -0,0 +1,168 @@
    +namespace Drupal\Tests\Core\TypedData;
    

    Should live in namespace Drupal\Tests\serialization\Unit\Normalizer.

    But this is a kernel test (ideally it'd be a unit test), so namespace Drupal\Tests\serialization\Kernel would be the place.

  5. +++ b/core/tests/Drupal/Tests/Core/TypedData/AnyNormalizeTest.php
    @@ -0,0 +1,168 @@
    + * @coversDefaultClass \Drupal\Tests\Core\TypedData\AnyNormalizeTest
    

    That's not the class it covers.

  6. +++ b/core/tests/Drupal/Tests/Core/TypedData/AnyNormalizeTest.php
    @@ -0,0 +1,168 @@
    +class AnyNormalizeTest extends KernelTestBase {
    

    Should be renamed to AnyNormalizerTest.

  7. +++ b/core/tests/Drupal/Tests/Core/TypedData/AnyNormalizeTest.php
    @@ -0,0 +1,168 @@
    +  protected function buildDataWithObjectTraversable() {
    

    We should also test with a basic object, which is *not* instanceof Traversable.

In other words: I think that we can simplify this patch a bit: if we make it only deal with $value instanceof Traversable || is_array($value), then this normalizer becomes much simpler, therefore less dangerous and much more logical.

wim leers’s picture

StatusFileSize
new2.03 KB
new6.5 KB

I've done the trivial stuff for you: this fixes points 1, 4, 5 and 6.

Points 2, 3 and 7 can all be fixed by doing

In other words: I think that we can simplify this patch a bit: if we make it only deal with $value instanceof Traversable || is_array($value), then this normalizer becomes much simpler, therefore less dangerous and much more logical.

wim leers’s picture

As of #2916252-52: [PP-1] Order's Adjustment can't be normalized and serialized, this blocks #2916252, which is a contrib issue.

lawxen’s picture

StatusFileSize
new7.57 KB
new5.24 KB

@Wim Leers Thanks for your patient guidance.

Change:

  1. Optimize AnyNormalizer's comment
  2. Add test testNormalizeNonNormalizableObject and testNormalizeEntity

Reply:

  1. But the if-test only tests is_object(). Shouldn't that then test if ($value instanceof Traversable || $value instanceof TypedDataInterface)?
    Also, why can't we do only $value instanceof Traversable?

    AnyNomarlizer use Symfony\Component\Serializer

        public function normalize($data, $format = null, array $context = array())
        {
            // If a normalizer supports the given data, use it
            if ($normalizer = $this->getNormalizer($data, $format)) {
                return $normalizer->normalize($data, $format, $context);
            }
    
            if (null === $data || is_scalar($data)) {
                return $data;
            }
    
            if (is_array($data) || $data instanceof \Traversable) {
                $normalized = array();
                foreach ($data as $key => $val) {
                    $normalized[$key] = $this->normalize($val, $format, $context);
                }
    
                return $normalized;
            }
    
            if (is_object($data)) {
                if (!$this->normalizers) {
                    throw new LogicException('You must register at least one normalizer to be able to normalize objects.');
                }
    
                throw new UnexpectedValueException(sprintf('Could not normalize object of type %s, no supporting normalizer found.', get_class($data)));
            }
    
            throw new UnexpectedValueException(sprintf('An unexpected value could not be normalized: %s', var_export($data, true)));
        }
    

    The Symfony\Component\Serializer->normalize()
    will help to check if the value has corresponding normalizer, traversable or is an array.
    What AnyNormalzer should be down for object is to
    Continue normalizing object which can be normalized
    Of cause an non-normalizable object will come to an UnexpectedValueException.

    If we want to make it less dangerous. some possible resolution:

    1. Check if the object is normalizable in AnyNormalizer()
              if ($normalizer = $this->getNormalizer($data, $format)) {
                  return $normalizer->normalize($data, $format, $context);
              }
      

      This will add a repeated loop which has been checked in Symfony\Component\Serializer->normalize, low performance, deprecated.

    2. use
      ($value instanceof Traversable || $value instanceof TypedDataInterface || $value instanceof EntityInterface)
      These can't include all normalizable object, and these will prevent extension for some custom normalizer which want to nomralize special value, deprecated.

    After considering performance, full functional, expansibility, code quality, and risk
    I suggest don't change this part

    isset($value) && (is_object($value) || is_array($value))

  2. I don't understand this "must convert the array value to object in setValue() to make it can be denormalized" at all. Can you point to an example of that?

    What's a real world use case for this?

    I change these comment to

    Field type with 'any' typed data property definition which stored traversable object, is advised to convert posted value to object in setValue() to make it can be denormalized easily without writing custom code to convert array to object when using api.
    

    And the real use is using RestApi or jsonapi to post a commerce order.
    AdjustmentIterm.php
    Comemrce order's commerce_adjustment field define an 'any' typed data with Adjustment object stored.
    When use jsonapi to post data to commerce order, If add the transaction code in setValue(),
    https://www.drupal.org/files/issues/2916252-51.patch

    +    // Used for denormalization.
    +    if (is_array($values)) {
    +      $values['amount'] = new Price($values['amount']['number'], $values['amount']['currency_code']);
    +      $values = new Adjustment($values);
    +    }
    

    the posted array value will be denormalized to Adjustment object, then be denormalized to commerce_adjustment field.
    without writing custom code in drupal.
    (Android app or Frontend can't construct an drupal object.)

lawxen’s picture

Status: Needs work » Needs review

Status: Needs review » Needs work

The last submitted patch, 23: 2915705-23.patch, failed testing. View results

lawxen’s picture

StatusFileSize
new7.51 KB
new7.57 KB

Add drupal8.4 version patch to make the update risk fall to minimum with the corresponding module's update like #2916252

  1. The 2915705-26-85x.patch is same with #23
  2. 2915705-26-84x.patch without this line code
    $this->addCacheableDependency($context, $object);
lawxen’s picture

Status: Needs work » Needs review
wim leers’s picture

Wow, great comment!

And thank you for your patience in waiting for feedback, I'm sorry for not reviewing your patch faster! Your persistence and attention to detail is impressive :) 👍

  1. Thanks for pointing out that \Symfony\Component\Serializer\Serializer::normalize() already does the if (is_array($data) || $data instanceof \Traversable) { check.

    You said

    Continue normalizing object which can be normalized

    When does Any contain TypedData objects? If it contains TypedData objects, it seems like it was wrong to use Any in the first place?

    In other words: can you point to a an existing/real example of this?

  2. But Adjustment does not implemented TypedDataInterface nor EntityInterface. The patch I proposed at #2916252-53: [PP-1] Order's Adjustment can't be normalized and serialized makes it implement \Traversable.

    Does that mean we don't need this patch at all? I queued #2916252-54: [PP-1] Order's Adjustment can't be normalized and serialized for testing to find out. If that passes, we don't need this patch.

lawxen’s picture

@Wim Leers

  1. I can't find existing/real example of store typedData or entity to 'any' typed data,
    So I change the comment to make it more appropriate
        // If the value is object or array, continue normalize it, object must be
        // normalizable. Traversable object is one of the object which can be
        // normalized, field type with typed data 'any' property definition which
        // stored the traversable object is advised to convert posted data to object
        // in setValue(), so that data can be denormalized automatically without
        // writing custom code to convert data to object when using machine readable
        // APIs.
    
  2. #2916252-54: [PP-1] Order's Adjustment can't be normalized and serialized
    Must combine this patch to make Adjustment can be output successful in jsonapi(jsonapi/commerce_order/default) or restapi.
    so whether the test pass has nothing to do with whether we need this patch
wim leers’s picture

Status: Needs review » Needs work

I apologize for having lost track of this issue :( Thanks to @skyredwang for pinging me about it via https://twitter.com/skyredwang/status/948380889980915713!


  1. Thanks, that's a
  2. You're right, sorry!

The most important thing that is still missing here is integration test coverage. The \Drupal\field_test\Plugin\Field\FieldType\TestObjectItem field type contains a non-computed property using the Any @DataType. So I propose that you write a \Drupal\Tests\datetime\Functional\EntityResource\EntityTest\EntityTestAnyTest that adds a field of that type to the EntityTest entity type, much like \Drupal\Tests\datetime\Functional\EntityResource\EntityTest\EntityTestDateonlyTest or \Drupal\Tests\datetime\Functional\EntityResource\EntityTest\EntityTestDatetimeTest already do. Then you can prove the correct behavior.

OTOH, that won't do something like [#12358670-54], so it's probably better to write another field type or entity type that does something more like Drupal Commerce's Adjustment.

wim leers’s picture

Also: I don't quite trust the current patch (neither the implementation nor the test coverage), because I can quite easily make it pass while making its normalizer a no-op.

Apply this change to #29, and run tests, and you'll see that it still passes…

diff --git a/core/modules/serialization/src/Normalizer/AnyNormalizer.php b/core/modules/serialization/src/Normalizer/AnyNormalizer.php
index 2c923c7..30105a8 100644
--- a/core/modules/serialization/src/Normalizer/AnyNormalizer.php
+++ b/core/modules/serialization/src/Normalizer/AnyNormalizer.php
@@ -18,6 +18,8 @@ class AnyNormalizer extends NormalizerBase {
    * {@inheritdoc}
    */
   public function normalize($object, $format = NULL, array $context = []) {
+    return $this->serializer->normalize($object, $format, $context);
+
     $this->addCacheableDependency($context, $object);
     $value = $object->getValue();
     // If the value is object or array, continue normalize it, object must be

lawxen’s picture

@Wim Leers Thanks for the advice
#31

  1. I can quite easily make it pass while making its normalizer a no-op.

    AnyNomalizer has the ability to normalize object, but not just object, so It meets expectation.

  2. diff --git a/core/modules/serialization/src/Normalizer/AnyNormalizer.php b/core/modules/serialization/src/Normalizer/AnyNormalizer.php
    index 2c923c7..30105a8 100644
    --- a/core/modules/serialization/src/Normalizer/AnyNormalizer.php
    +++ b/core/modules/serialization/src/Normalizer/AnyNormalizer.php
    @@ -18,6 +18,8 @@ class AnyNormalizer extends NormalizerBase {
    * {@inheritdoc}
    */
    public function normalize($object, $format = NULL, array $context = []) {
    + return $this->serializer->normalize($object, $format, $context);
    +
    $this->addCacheableDependency($context, $object);
    $value = $object->getValue();
    // If the value is object or array, continue normalize it, object must be

    These code will make

      public function testNormalizeBase() {
        $typed_data = $this->buildDataBasicString();
        $this->assertSame('test', $this->serializer->normalize($typed_data, 'json'));
      }

    be an endless loop, So it can't pass this test.

#30

  1. it's probably better to write another field type or entity type that does something more like Drupal Commerce's Adjustment.

    Good advice, I will do it later.

wim leers’s picture

AnyNomalizer has the ability to normalize object, but not just object, so It meets expectation.

If the change I show in #31 still passes tests, it means we have no tests that prove the need for this. That's why I said

it's probably better to write another field type or entity type that does something more like Drupal Commerce's Adjustment.

Looks like you're going to write that test, great :) Once you've written that test, a change like the one I made in #31 should fail!

lawxen’s picture

@Wim Leers

Once you've written that test, a change like the one I made in #31 should fail!

The change in #31

   public function normalize($object, $format = NULL, array $context = []) {
+    return $this->serializer->normalize($object, $format, $context);
+

can't make test: \Drupal\Tests\serialization\Kernel\AnyNormalizerTest pass.

Version: 8.5.x-dev » 8.6.x-dev

Drupal 8.5.0-alpha1 will be released the week of January 17, 2018, which means new developments and disruptive changes should now be targeted against the 8.6.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

lawxen’s picture

StatusFileSize
new13.38 KB
new5.21 KB

Construct a field and then Add rest test for AnyNormalize, @Wim Leers thanks for the guidance on #30,
But it still has a failing test of post waiting to be solved:

Failed asserting that an array has the subset Array &0 (
    0 => Array &1 (
        'value' => Array &2 (
            'property1' => 'value1'
            'property2' => 'value2'
        )
    )
).
 /app/www/any86/core/modules/rest/tests/src/Functional/EntityResource/EntityResourceTestBase.php:918
lawxen’s picture

Status: Needs work » Needs review
StatusFileSize
new15 KB
new2.72 KB

Fix post and patch failing test of #36:

Failed asserting that an array has the subset Array &0 (
    0 => Array &1 (
        'value' => Array &2 (
            'property1' => 'value1'
            'property2' => 'value2'
        )
    )
).
 /app/www/any86/core/modules/rest/tests/src/Functional/EntityResource/EntityResourceTestBase.php:918

Status: Needs review » Needs work

The last submitted patch, 37: 2915705-37.patch, failed testing. View results

lawxen’s picture

Status: Needs work » Needs review
StatusFileSize
new15.02 KB
new564 bytes

Oh, a failing test happen, this patch fix it.

lawxen’s picture

Status: Needs review » Needs work

I don't think the way i handle the denormalization is appropriate

Version: 8.6.x-dev » 8.7.x-dev

Drupal 8.6.0-alpha1 will be released the week of July 16, 2018, which means new developments and disruptive changes should now be targeted against the 8.7.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

berdir’s picture

Wondering how the new approach in #2895532: @DataType=map cannot be normalized, affects @FieldType=link, @FieldType=map will affect this, might just work for this as well...

wim leers’s picture

Status: Needs work » Needs review
StatusFileSize
new13.78 KB

Rebased now that #2895532: @DataType=map cannot be normalized, affects @FieldType=link, @FieldType=map landed.

might just work for this as well...

Indeed! So I tried. Unfortunately it's still necessary. At least the changes that this patch was making to EntityResourceTestBase are no longer necessary, #2895532: @DataType=map cannot be normalized, affects @FieldType=link, @FieldType=map improved that in a superior way 🙏

Status: Needs review » Needs work

The last submitted patch, 43: 2915705-43.patch, failed testing. View results

lawxen’s picture

Status: Needs work » Needs review
StatusFileSize
new14.93 KB
new12.59 KB
  1. Make the test green by making this change:
    --- a/core/modules/rest/tests/src/Functional/EntityResource/EntityResourceTestBase.php
    +++ b/core/modules/rest/tests/src/Functional/EntityResource/EntityResourceTestBase.php
    @@ -1524,7 +1524,7 @@ protected function assertStoredEntityMatchesSentNormalization(array $sent_normal
             // Subset, not same, because we can e.g. send just the target_id for the
             // bundle in a PATCH or POST request; the response will include more
             // properties.
    -        $this->assertArraySubset($expected_field_normalization, $modified_entity->get($field_name)->getValue(), TRUE);
    +        $this->assertArraySubset($expected_field_normalization, $this->serializer->normalize($modified_entity->get($field_name)), TRUE);
           }
         }
       }
    
  2. At least the changes that this patch was making to EntityResourceTestBase are no longer necessary, #2895532: @DataType=map cannot be normalized, affects @FieldType=link, @FieldType=map improved that in a superior way 🙏

    Move to Drupal\Tests\entity_test and use Drupal\Tests\entity_test\Functional\Rest\EntityTestResourceTestBase directly.

Status: Needs review » Needs work

The last submitted patch, 45: 2915705-45b.patch, failed testing. View results

lawxen’s picture

StatusFileSize
new19.5 KB
new10.59 KB
  1. Make the test green by making this change:
    --- a/core/modules/rest/tests/src/Functional/EntityResource/EntityResourceTestBase.php
    +++ b/core/modules/rest/tests/src/Functional/EntityResource/EntityResourceTestBase.php
    @@ -1524,7 +1524,7 @@ protected function assertStoredEntityMatchesSentNormalization(array $sent_normal
    // Subset, not same, because we can e.g. send just the target_id for the
    // bundle in a PATCH or POST request; the response will include more
    // properties.
    - $this->assertArraySubset($expected_field_normalization, $modified_entity->get($field_name)->getValue(), TRUE);
    + $this->assertArraySubset($expected_field_normalization, $this->serializer->normalize($modified_entity->get($field_name)), TRUE);
    }
    }
    }

    oh, this change make so many tests fail, delete it first

  2. Use EntityResourceTestBase instead like ‘map' did.

Handle the failure test later:

Failed asserting that an array has the subset Array &0 (
    0 => Array &1 (
        'value' => Array &2 (
            'property1' => 'value1'
            'property2' => 'value2'
        )
    )
). <Click to see difference>
lawxen’s picture

Assigned: lawxen » Unassigned
lawxen’s picture

Status: Needs work » Needs review
StatusFileSize
new20.43 KB
new1.4 KB

IN #45 I missed ->getValue() in

-        $this->assertArraySubset($expected_field_normalization, $modified_entity->get($field_name)->getValue(), TRUE);
+        $this->assertArraySubset($expected_field_normalization, $this->serializer->normalize($modified_entity->get($field_name)), TRUE);

So it caused so many failure test.

lawxen’s picture

StatusFileSize
new17.64 KB
lawxen’s picture

2915705-43.patch --> 2915705-47.patch
Add change log:

  1. -        $this->assertArraySubset($expected_field_normalization, $modified_entity->get($field_name)->getValue(), TRUE);
    +        $this->assertArraySubset($expected_field_normalization, $this->serializer->normalize($modified_entity->get($field_name)->getValue()), TRUE);
    

    Add this change to make the test 2915705-43.patch green.

  2. Because Drupal\Tests\rest\Functional\EntityResource\EntityTest\EntityTestResourceTestBase has been deprecated, so Changed to Drupal\Tests\rest\Functional\EntityResource\EntityResourceTestBase just like map did.
    1. EntityTestAnyFieldResourceTestBase
    2. EntityTestAnyFieldJsonAnonTest
    3. EntityTestAnyFieldHalJsonAnonTest

    Above three tests was copied from 'Map' and made some minimized change.

lawxen’s picture

StatusFileSize
new20.97 KB
new4.11 KB

When I write resource test for commerce's adjustment field.
I find EntityResourceTestBase didn't handle boolean value properly

      $expected = static::castToString($expected);
      static::recursiveKSort($expected);
      $actual = $this->serializer->decode((string) $response->getBody(), static::$format);
      static::recursiveKSort($actual);
      $this->assertSame($expected, $actual);
        $expected_field_normalization = ($field_type !== 'map')
          ? static::castToString($field_normalization)
          : $field_normalization;
        // Subset, not same, because we can e.g. send just the target_id for the
        // bundle in a PATCH or POST request; the response will include more
        // properties.
        $this->assertArraySubset($expected_field_normalization, $modified_entity->get($field_name)->getValue(), TRUE);

Boolean value just be cast to string on "expected", not "actual" on the above two places.

This patch fix it and add corresponding tests.

Status: Needs review » Needs work

The last submitted patch, 52: 2915705-52.patch, failed testing. View results

wim leers’s picture

Thanks for pushing this forward again! Let's get this done this time, now that #2895532: @DataType=map cannot be normalized, affects @FieldType=link, @FieldType=map is finally committed.

Unfortunately, #52 failed, which seems to be the opposite of what you expected?

lawxen’s picture

StatusFileSize
new21.77 KB
new1.34 KB

@Wim Leers
This will fix the failure test of #52 😊

lawxen’s picture

Status: Needs work » Needs review

😍 test green @Wim Leers

lawxen’s picture

[Typed data layout_section can't be normalized if it stores object](https://www.drupal.org/project/drupal/issues/3012165#comment-12847218)

Typed data layout_section has the same problem with this issue, @Wim Leers what about merging the two issue with one and do sth in TypedDataNormalizer directly.

Two methods:

  1. Merging the two issue with one and do sth in TypedDataNormalizer directly.
  2. Two Normalizer: AnyNormalzier and LayoutSectionNormalizer

Version: 8.7.x-dev » 8.8.x-dev

Drupal 8.7.0-alpha1 will be released the week of March 11, 2019, which means new developments and disruptive changes should now be targeted against the 8.8.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

dravenk’s picture

StatusFileSize
new21.18 KB
new2.17 KB

Just re-roll patch #55

Status: Needs review » Needs work

The last submitted patch, 59: 2915705-59.patch, failed testing. View results

Version: 8.8.x-dev » 8.9.x-dev

Drupal 8.8.0-alpha1 will be released the week of October 14th, 2019, which means new developments and disruptive changes should now be targeted against the 8.9.x-dev branch. (Any changes to 8.9.x will also be committed to 9.0.x in preparation for Drupal 9’s release, but some changes like significant feature additions will be deferred to 9.1.x.). For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

yonailo’s picture

What are we waiting for in this issue patch to be it commited ? I see the latest patch fails in two tests related to "EntityTestDatetimeTest", but it seems not related to this patch, I don't understand at all the tests but can someone look at this ?

I have found this issue when using content_sync module + drupal commerce, and a fix for drupal commerce (#2916252) is waiting for this to be commited... :(

Version: 8.9.x-dev » 9.1.x-dev

Drupal 8.9.0-beta1 was released on March 20, 2020. 8.9.x is the final, long-term support (LTS) minor release of Drupal 8, which means new developments and disruptive changes should now be targeted against the 9.1.x-dev branch. For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

Version: 9.1.x-dev » 9.2.x-dev

Drupal 9.1.0-alpha1 will be released the week of October 19, 2020, which means new developments and disruptive changes should now be targeted for the 9.2.x-dev branch. For more information see the Drupal 9 minor version schedule and the Allowed changes during the Drupal 9 release cycle.

Version: 9.2.x-dev » 9.3.x-dev

Drupal 9.2.0-alpha1 will be released the week of May 3, 2021, which means new developments and disruptive changes should now be targeted for the 9.3.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

sch4lly’s picture

Status: Needs work » Needs review
StatusFileSize
new21.57 KB

Re-rolled to 9.3.0

spokje’s picture

Status: Needs review » Needs work
spokje’s picture

Used 2915705-59.patch for an MR with a reroll on 9.3.x

spokje’s picture

Assigned: Unassigned » spokje
Issue tags: -Needs tests
spokje’s picture

Assigned: spokje » Unassigned
spokje’s picture

Assigned: Unassigned » spokje
spokje’s picture

Assigned: spokje » Unassigned
bbrala’s picture

Bookmarking this for myself

spokje’s picture

Just a note: I believe all the blocker tags are outdated.

I came here to get Drupal Commerce order adjustment working with the Core RESTful Web Services.

Turns out enabling Core JSON:API and adding commerce_api to my project, this works out of the box, even when just using REST and nothing of the JSON:API "shizzle".

That also probably explains the lack of activity here.

bbrala’s picture

Hmm, I wonder... Does jsonapi fix it or the commerce_api? I know there is quite a few overwritten stuff in the commerce_api.

skyredwang’s picture

Does jsonapi fix it or the commerce_api? I know there is quite a few overwritten stuff in the commerce_api.

Commerce API has its own solution. This ticket is for the core issue, which will not only affect JSON:API, but also default_content. (Can't remember too much detail.)

berdir’s picture

default_content 2.x isn't using the core serialization API anymore, so that's not affected by this either I think.

Version: 9.3.x-dev » 9.4.x-dev

Drupal 9.3.0-rc1 was released on November 26, 2021, which means new developments and disruptive changes should now be targeted for the 9.4.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.4.x-dev » 9.5.x-dev

Drupal 9.4.0-alpha1 was released on May 6, 2022, which means new developments and disruptive changes should now be targeted for the 9.5.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.5.x-dev » 10.1.x-dev

Drupal 9.5.0-beta2 and Drupal 10.0.0-beta2 were released on September 29, 2022, which means new developments and disruptive changes should now be targeted for the 10.1.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

jurgenhaas made their first commit to this issue’s fork.

jurgenhaas’s picture

StatusFileSize
new20.78 KB

Fixed a spacing problem in the MR so that the patch file applies to D10.

spokje’s picture

Thanks @jurgenhaas, rebased MR on 10.1.x.

Version: 10.1.x-dev » 11.x-dev

Drupal core is moving towards using a “main” branch. As an interim step, a new 11.x branch has been opened, as Drupal.org infrastructure cannot currently fully support a branch named main. New developments and disruptive changes should now be targeted for the 11.x branch, which currently accepts only minor-version allowed changes. For more information, see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

rhovland made their first commit to this issue’s fork.

rhovland changed the visibility of the branch 10.1.x to hidden.

rhovland changed the visibility of the branch 11.x to hidden.

yevko’s picture

StatusFileSize
new20.82 KB

Patch that doesn't invoke an error on 11.2.3.

Declaration of Drupal\serialization\Normalizer\AnyNormalizer::normalize($object, $format = null, array $context = []) must be compatible with Symfony\Component\Serializer\Normalizer\NormalizerInterface::normalize(mixed $data, ?string $format = null, array $context = []): ArrayObject|array|string|int|float|bool|null in /app/public/core/modules/serialization/src/Normalizer/AnyNormalizer.php on line 20

Version: 11.x-dev » main

Drupal core is now using the main branch as the primary development branch. New developments and disruptive changes should now be targeted to the main branch.

Read more in the announcement.

rhovland changed the visibility of the branch 2915705-typeddata-any-cant to hidden.

rhovland changed the visibility of the branch main to hidden.