IntegerField() is converted to string in JSON for any number

723 views Asked by At

This question is very similar to this one, only worse... In my tests, even small integers such as 1 are converted to strings. This is my MWE:

app.yaml

application: your-app-id
version: 1
runtime: python27
threadsafe: true
api_version: 1

handlers:
- url: /_ah/spi/.*
  script: example.app
  secure: always

libraries:
- name: endpoints
  version: 1.0

example.py

import endpoints
from protorpc import messages
from protorpc import message_types
from protorpc import remote

package = 'Example'

class Number(messages.Message):
    number = messages.IntegerField(1)

@endpoints.api(name='example', version='v1')
class ExampleApi(remote.Service):
    @endpoints.method(message_types.VoidMessage, Number,
            path = "getNumber", http_method='GET', name = 'getNumber')
    def get_number(self, _):
        return Number(number = 3)  # This is a small integer. Isn't?

app = endpoints.api_server([ExampleApi])

Test

$ curl http://localhost:8080/_ah/api/example/v1/getNumber
{
 "number": "3"
}

Why? Should't it be { "number": 3 } instead? This is forcing me to reconvert all my integers in the client end if I want to do arithmetic with them.

(Tested with GAE SDK version 1.9.21 - 2015-05-12, both in Linux and OSX with the "native" dmg. Edit: also tested in production, deployed in GAE servers, same results... integers are received as strings)

Update

Even if my simple example returns integers as strings (as shown above), Google's own APIs don't do that. They do the right thing. For example, using the books api via the API explorer, here:

https://apis-explorer.appspot.com/apis-explorer/#p/books/v1/books.volumes.list?q=Google&_h=8&

you get a JSON answer which contains properly encoded integers, such as:

{
 "kind": "books#volumes",
 "totalItems": 551,
 "items": [ ... ]
  ...
}

So their APIs are returning good json. Why my API, made with the same tools (or perhaps not?), is returning integers as strings istead? I consider this a bug.

1

There are 1 answers

1
Josh J On BEST ANSWER

It looks like the default variant for an IntegerField is INT64 which gets encoded into a string inside of EndpointsProtoJson.encode_field

def encode_field(self, field, value):
    """Encode a python field value to a JSON value.

    Args:
      field: A ProtoRPC field instance.
      value: A python value supported by field.

    Returns:
      A JSON serializable value appropriate for field.
    """


    if (isinstance(field, messages.IntegerField) and
        field.variant in (messages.Variant.INT64,
                          messages.Variant.UINT64,
                          messages.Variant.SINT64)):
      if value not in (None, [], ()):

        if isinstance(value, list):
          value = [str(subvalue) for subvalue in value]
        else:
          value = str(value)
        return value

You can change this behavior by specifying it to be a 32bit integer in your messages.Message subclass.

class Number(messages.Message):
    number = messages.IntegerField(1, variant=messages.Variant.INT32)