Home > Enterprise >  Exposing JSON numbers with Doctrine decimal type using API Platform
Exposing JSON numbers with Doctrine decimal type using API Platform

Time:08-27

I am creating a web API using API Platform and entities are stored in a SQL database using Doctrine ORM.

Doctrine maps decimal type to PHP string. However, I'd like to expose decimal fields as JSON numbers while retaining the NUMERIC type in my SQL database. I am well aware that some precision can be lost using JSON numbers but I am OK to ignore it.

I tried using a getter and a setter for the decimal field(s):

#[ORM\Entity]
#[ApiResource]
class Product
{
    #[ORM\Column(type: 'decimal', precision=10, scale=2)]
    private string $price = '0';

    public function getPrice(): float
    {
        return (float) $this->price;
    }

    public function setPrice(float|string $price): self
    {
        if (is_float($price)) {
            $this->price = number_format($price, 2, '.', '');
        }
        else {
            $this->price = $price;
        }

        return $this;
    }
}

When serializing the entity to JSON/JSON-LD, the price field is correctly serialized to a number using getPrice():

{
  "@id": "/api/products/1",
  "@type": "Product",
  "price": 65.3
}

However creating or updating a product with a number for the price value throws an error:

curl -X 'POST' \
  "http://localhost:8000/api/products" \
  -H 'Content-Type: application/ld json' \
  -d '{
  "price": 120.6
}'
The type of the "price" attribute must be "string", "integer" given.

Edit: The setter is never called. The error is thrown by ApiPlatform\Core\Serializer\AbstractItemNormalizer from ValidateType() which is called by CreateAttributeValue().

Related: Doctrine GitHub issue

CodePudding user response:

The error is thrown by the normalizer type checker (ApiPlatform\Core\Serializer\AbstractItemNormalizer in CreateAttributeValue() that call ValidateType().

This can be circumvented by adding ObjectNormalizer::DISABLE_TYPE_ENFORCEMENT to the normalizer context:

use Symfony\Component\Serializer\Annotation\Context;

#[ORM\Entity]
#[ApiResource]
class Product
{
    #[Context(['disable_type_enforcement' => true])]
    #[ORM\Column(type: 'decimal', precision=10, scale=2)]
    private string $price = '0';

    public function getPrice(): float
    {
        return (float) $this->price;
    }

    public function setPrice(float|string $price): self
    {
        if (is_float($price)) {
            $this->price = number_format($price, 2, '.', '');
        }
        else {
            $this->price = $price;
        }

        return $this;
    }
}

CodePudding user response:

Probably you can cast the price as a string like this:

#[ORM\Entity]
#[ApiResource]
class Product
{
    #[ORM\Column(type: 'decimal', precision=10, scale=2)]
    private string $price = '0';

    public function getPrice(): float
    {
        return (float) $this->price;
    }

    public function setPrice(float|string $price): self
    {
        if (is_float($price)) {
            $this->price = (string) number_format($price, 2, '.', ''); //Casting to string
        }
        else {
            $this->price = (string) $price; //Casting to string
        }

        return $this;
    }
}
  • Related