glideapps/quicktype

The Python "to_dict" method fails when generated from JSON schema with properties of type "number" which are assigned an integer value

douggish opened this issue · 0 comments

Consider the example JSON schema:

{
  "id": "http://json-schema.org/geo",
  "$schema": "http://json-schema.org/draft-06/schema#",
  "description": "A geographical coordinate",
  "type": "object",
  "properties": {
    "latitude": {
      "type": "number"
    },
    "longitude": {
      "type": "number"
    }
  }
}

Quicktype generates the following Python code:

from dataclasses import dataclass
from typing import Optional, Any, TypeVar, Type, cast


T = TypeVar("T")


def from_float(x: Any) -> float:
    assert isinstance(x, (float, int)) and not isinstance(x, bool)
    return float(x)


def from_none(x: Any) -> Any:
    assert x is None
    return x


def from_union(fs, x):
    for f in fs:
        try:
            return f(x)
        except:
            pass
    assert False


def to_float(x: Any) -> float:
    assert isinstance(x, float)
    return x


def to_class(c: Type[T], x: Any) -> dict:
    assert isinstance(x, c)
    return cast(Any, x).to_dict()


@dataclass
class Coordinate:
    """A geographical coordinate"""
    latitude: Optional[float] = None
    longitude: Optional[float] = None

    @staticmethod
    def from_dict(obj: Any) -> 'Coordinate':
        assert isinstance(obj, dict)
        latitude = from_union([from_float, from_none], obj.get("latitude"))
        longitude = from_union([from_float, from_none], obj.get("longitude"))
        return Coordinate(latitude, longitude)

    def to_dict(self) -> dict:
        result: dict = {}
        if self.latitude is not None:
            result["latitude"] = from_union([to_float, from_none], self.latitude)
        if self.longitude is not None:
            result["longitude"] = from_union([to_float, from_none], self.longitude)
        return result


def coordinate_from_dict(s: Any) -> Coordinate:
    return Coordinate.from_dict(s)


def coordinate_to_dict(x: Coordinate) -> Any:
    return to_class(Coordinate, x)

The problem is that if you create an instance of Coordinate with literal integer parameters and then call to_dict():

coordinate = Coordinate(10, 20)
print(coordinate.to_dict())

then you get an AssertionError:

Traceback (most recent call last):
  File "/tmp/sessions/4a8af4124d815246/main.py", line 71, in <module>
    print(coordinate.to_dict())
  File "/tmp/sessions/4a8af4124d815246/main.py", line 53, in to_dict
    result["latitude"] = from_union([to_float, from_none], self.latitude)
  File "/tmp/sessions/4a8af4124d815246/main.py", line 24, in from_union
    assert False
AssertionError

This should be expected to work since JSON schema spec defines the number type like this:

The number type is used for any numeric type, either integers or floating point numbers.

Also, note that in Python the "float" type hint also deems an int as acceptable:

Rather than requiring that users write import numbers and then use numbers.Float etc., this PEP proposes a straightforward shortcut that is almost as effective: when an argument is annotated as having type float, an argument of type int is acceptable