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

"TypeError: <amount> <currency> is not JSON serializable" when serializing a calculated model fields in DRF #291

Closed
JadSayegh opened this issue May 14, 2017 · 6 comments

Comments

@JadSayegh
Copy link

I have model which has a calculated MoneyField:

class Account(models.Model):
    deposits = MoneyField(decimal_places=2, max_digits=14, default=Money(0, 'USD'))
    withdrawals = MoneyField(decimal_places=2, max_digits=14, default=Money(0, 'USD'))

    @property
    def net_deposits(self):
        return Money(self.deposits.amount - self.withdrawals.amount, self.deposits.currency)

My serializer looks like this

class AccountSerializer(serializers.ModelSerializer):
    net_deposits = serializers.ReadOnlyField()

    class Meta:
        model = Account
        fields = ('deposits', 'withdrawals', 'net_deposits')

Whenever I try to query a model through this serializer I get the error mentioned in the title, for example:
TypeError: 80 USD is not JSON serializable

This is not an issue when the field is directly on the model (not a calculated field).

I've also tried making it a SerializerMethodField and return net_deposit from the function and am getting the same thing. What's the correct way to get this to work?

@Stranger6667
Copy link
Collaborator

Hello @JadSayegh !
You have to explicitly declare MoneyField on AccountSerializer.

class AccountSerializer(serializers.ModelSerializer):
    net_deposits = MoneyField(max_digits=14, decimal_places=2, read_only=True)

    class Meta:
        model = Account
        fields = ('deposits', 'withdrawals', 'net_deposits')

And it will process the money value as usual:

>>> instance = Account.objects.create(deposits=Money(1, 'EUR'), withdrawals=Money(2, 'EUR'))
>>> serializer = AccountSerializer(instance=instance)
>>> serializer.data
{'deposits': '1.00', 'net_deposits': '-1.00', 'withdrawals': '2.00'}

I hope this will help :) If you have any questions - I'd like to hear from you :)

Best regards

@JadSayegh
Copy link
Author

JadSayegh commented May 14, 2017

Thanks for the quick reply Dmitry (@Stranger6667), my net_deposits are now showing up in my serialized data, but without net_deposits_currency. I tried adding that to the fields but I am getting an error as a result:

django.core.exceptions.ImproperlyConfigured: Field name net_deposits_currency is not valid for model 'Account'.

@Stranger6667
Copy link
Collaborator

Since there is no such field on the model you cant use it as an automatic field. But you can do something like this:

class AccountSerializer(serializers.ModelSerializer):
    net_deposits = MoneyField(max_digits=14, decimal_places=2, read_only=True)
    net_deposits_currency = serializers.CharField(source='net_deposits.currency')

    class Meta:
        model = Account
        fields = ('deposits', 'withdrawals', 'net_deposits', 'net_deposits_currency')

And the currency should appear in the serialized data:

>>> instance = Account.objects.create(deposits=Money(1, 'EUR'), withdrawals=Money(2, 'EUR'))
>>> serializer = AccountSerializer(instance=instance)
>>> serializer.data
{'deposits': '1.00', 'net_deposits': '-1.00', 'withdrawals': '2.00', 'net_deposits_currency': 'EUR'}

@JadSayegh
Copy link
Author

Awesome, that solves it!

Clarification note for future readers: MoneyField is the one in djmoney.contrib.django_rest_framework and not the one in djmoney.models.fields.

Out of curiosity, if I had a SerializerMethodField that returns a Money object, how would I serialize that? Would the only way be to have the method on the model class as a @property like what I did with net_deposits or is there a way to use a method on the serializer?

@Stranger6667
Copy link
Collaborator

Hm, it depends on the context and your personal taste. From some point of view, the object is serialized - you have field and its value (Money instance). Further, if you want to convert the serialized result to JSON you could create your own JSONEncoder (like Django does) and convert this value to some valid JSON output.

Another option - return dictionary from this method instead of Money, but it will be nested structure. In case if you want "flat" structure DRF implies, that you should use separate field for this. But you can unnest this structure later in the serialization process, but it is a bit hacky IMO.

So, there are a plenty of options to do serialization :)

Hope, I've answered your question

Best Regards

@Stranger6667
Copy link
Collaborator

I'm closing this issue :) If you have any questions feel free to reopen this or create a new one

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

2 participants