Skip to content

Commit

Permalink
Support Decimal fields while deserializing. (#903)
Browse files Browse the repository at this point in the history
Support Decimal fields while de-serializing and add section to docs.

Co-authored-by: David Glick <[email protected]>
  • Loading branch information
jensens and davisagli authored Mar 6, 2023
1 parent 11fdc5d commit 31a18e1
Show file tree
Hide file tree
Showing 7 changed files with 44 additions and 10 deletions.
18 changes: 15 additions & 3 deletions docs/source/usage/serialization.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,16 +31,28 @@ Since JSON does not have native support for dates and times, the Python and Zope
| `DateTime("2015/11/23 19:45:55")` | `"2015-11-23T19:45:55"` |


## Decimal Type

The [Python Decimal type](https://docs.python.org/3/library/decimal.html) supports correctly rounded decimal floating point arithmetic.
To keep the precision, serializing the value to JSON results in a string.

| Python | JSON |
| ------------------------------------ | ----------------------- |
| `Decimal("3.14159265359")` | `"3.14159265359"` |


## RichText fields

RichText fields will be serialized as follows:

A `RichTextValue` such as the following:

```python
RichTextValue(u'<p>Hallöchen</p>',
mimeType='text/html',
outputMimeType='text/html')
RichTextValue(
"<p>Hallöchen</p>",
mimeType="text/html",
outputMimeType="text/html",
)
```

…will be serialized to:
Expand Down
2 changes: 2 additions & 0 deletions news/903.bugfix
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Fix missing `Decimal` field deserializer.
[jensens]
1 change: 1 addition & 0 deletions src/plone/restapi/deserializer/configure.zcml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
<adapter factory=".dxfields.TimedeltaFieldDeserializer" />
<adapter factory=".dxfields.NamedFieldDeserializer" />
<adapter factory=".dxfields.RichTextFieldDeserializer" />
<adapter factory=".dxfields.DecimalFieldDeserializer" />
<adapter factory=".blocks.BlocksJSONFieldDeserializer" />

<subscriber
Expand Down
12 changes: 12 additions & 0 deletions src/plone/restapi/deserializer/dxfields.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from datetime import timedelta
from decimal import Decimal
from plone.app.contenttypes.interfaces import ILink
from plone.app.textfield.interfaces import IRichText
from plone.app.textfield.value import RichTextValue
Expand All @@ -18,6 +19,7 @@
from zope.schema.interfaces import IChoice
from zope.schema.interfaces import ICollection
from zope.schema.interfaces import IDatetime
from zope.schema.interfaces import IDecimal
from zope.schema.interfaces import IDict
from zope.schema.interfaces import IField
from zope.schema.interfaces import IFromUnicode
Expand Down Expand Up @@ -296,3 +298,13 @@ def __call__(self, value):
)
self.field.validate(value)
return value


@implementer(IFieldDeserializer)
@adapter(IDecimal, IDexterityContent, IBrowserRequest)
class DecimalFieldDeserializer(DefaultFieldDeserializer):
def __call__(self, value):
if not isinstance(value, Decimal):
value = Decimal(value)
self.field.validate(value)
return value
3 changes: 0 additions & 3 deletions src/plone/restapi/tests/dxtypes.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,6 @@ def vocabularyRequireingContextFactory(context):


class IDXTestDocumentSchema(model.Schema):

# zope.schema fields
test_ascii_field = schema.ASCII(required=False)
test_asciiline_field = schema.ASCIILine(required=False)
Expand Down Expand Up @@ -310,13 +309,11 @@ class DXTestDocument(Item):

@provider(IFormFieldProvider)
class ITestBehavior(model.Schema):

test_behavior_field = schema.TextLine(required=False)
# Add nav_title to test if it gets substituted in Navigation service
nav_title = schema.TextLine(required=False)


@provider(IFormFieldProvider)
class ITestAnnotationsBehavior(model.Schema):

test_annotations_behavior_field = schema.TextLine(required=False)
10 changes: 10 additions & 0 deletions src/plone/restapi/tests/test_dxfield_deserializer.py
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,16 @@ def test_float_deserialization_returns_float(self):
self.assertTrue(isinstance(value, float), "Not a <float>")
self.assertEqual(1.0, value)

def test_float_deserialization_returns_decimal(self):
value = self.deserialize("test_decimal_field", 1.111)
self.assertTrue(isinstance(value, Decimal), "Not a <Decimal>")
# a float from JSON to decimal can not work properly, so you would get
# real floating point precision only
self.assertEqual(
Decimal("1.1109999999999999875655021241982467472553253173828125"),
value,
)

def test_frozenset_deserialization_returns_frozenset(self):
value = self.deserialize("test_frozenset_field", ["foo", "bar"])
self.assertTrue(isinstance(value, frozenset), "Not a <frozenset>")
Expand Down
8 changes: 4 additions & 4 deletions src/plone/restapi/tests/test_dxfield_serializer.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,10 +104,10 @@ def test_datetime_field_serialization_returns_unicode(self):
self.assertTrue(isinstance(value, str), "Not an <unicode>")
self.assertEqual("2015-06-20T13:22:04", value)

def test_decimal_field_serialization_returns_unicode(self):
value = self.serialize("test_decimal_field", Decimal("1.1"))
self.assertTrue(isinstance(value, str), "Not an <unicode>")
self.assertEqual("1.1", value)
def test_decimal_field_serialization_returns_str(self):
value = self.serialize("test_decimal_field", Decimal("1.111"))
self.assertTrue(isinstance(value, str), "Not an <str>")
self.assertEqual("1.111", value)

def test_dict_field_serialization_returns_dict(self):
value = self.serialize(
Expand Down

0 comments on commit 31a18e1

Please sign in to comment.