0

I have classes that I want to represent in JSON

In Javascript, a getter property can be included or excluded of a Json.stringfy() if defined as enumerable: true/false like this:

class Example {

    constructor(){
        Object.defineProperties(this, {
            notEnumerableProperty: {
                get: function () {
                    return 'this is not enumerable';
                },
                enumerable: false
            },
            enumerableProperty: {
                get: function () {
                    return 'this is a enumerable';
                },
                enumerable: true
            }
        });
    }

}

const instance = new Example;

JSON.stringify(instance);
// output "{"enumerableProperty":"this is a enumerable"}"

In Python we can define a getter function as a property like in Javascript using @property decorator. But it doesn't list in a JSON:

#JSONEncoder as seen in https://stackoverflow.com/questions/3768895/how-to-make-a-class-json-serializable
from json import JSONEncoder

class MyEncoder(JSONEncoder):
  def default(self, o):
      return o.__dict__    


#My class
class Example():

  def __init__(self):
    self.value = 'this value is enumerable'

  @property
  def enumerableProperty(self):
    return 'It cannot enumerable';

  @property
  def notEnumerableProperty(self):
    return 'It is not enumerable';

instance = Example()
toJson = MyEncoder().encode(instance)

print(toJson)
#output: {"value": "this value is enumerable"}

Is it possible to allow a property be enumerated into a JSON like in Javascript?

Paulo Henrique
  • 938
  • 1
  • 13
  • 19
  • Can you share more of your python code, and what exactly you're trying to accomplish in python? Are you trying to represent python objects in JSON notation? – Green Cloak Guy Jun 24 '19 at 19:30
  • See https://stackoverflow.com/questions/3768895/how-to-make-a-class-json-serializable for how you can customize the way a Python class is encoded in JSON. – Barmar Jun 24 '19 at 19:56
  • 1
    Um, `json.dumps` does not support user-defined objects, and you have to extend the JSON encoder class with a custom encoder, where you could do whatever you want. So *what exactly are you doing*? – juanpa.arrivillaga Jun 24 '19 at 20:02
  • I edited the question – Paulo Henrique Jun 24 '19 at 20:27

2 Answers2

1

this sort of thing is very "non-Pythonic", but you might be able to get some mileage out of creating a custom property decorator and then having a custom JSONEncoder that checks for this. the decorator would be something like:

class property_nonenum(property):
    __slots__ = {'__doc__'}

which we could check for in an encoder by doing:

import json

class JsonObjectEncoder(json.JSONEncoder):
    def default(self, obj):
        ignore = set()
        cls = type(obj)
        for name in dir(cls):
            if name.startswith('_'):
                continue
            x = getattr(cls, name, None)
            if callable(x):
                # don't want methods
                ignore.add(name)
            elif isinstance(x, property):
                # ignore properties that aren't enumerable
                if isinstance(x, property_nonenum):
                    ignore.add(name)
        result = {}
        for name in dir(obj):
            if name.startswith('_') or name in ignore:
                continue
            result[name] = getattr(obj, name)
        return result

and we can test this with a dummy class:

class Example:
    __slots__ = {'foo'}

    def __init__(self):
        self.foo = 10

    @property
    def bar(self):
        return 20

    @property_nonenum
    def baz(self):
        return 30

    def myfn(self):
        return 40

    myval = 50


print(json.dumps(Example(), cls=JsonObjectEncoder))

which gives me: {"bar": 20, "foo": 10, "myval": 50}

Sam Mason
  • 15,216
  • 1
  • 41
  • 60
0

You could use custom JSON encoder (doc) and use inspect.getmembers() to get properties of class (doc):

import inspect
import json

class my_class:
    def __init__(self):
        self.__foo = 'Hello World!'
        self.i = 111

    @property
    def foo(self):
        return self.__foo

class MyEncoder(json.JSONEncoder):
    def default(self, o):
        properties = [(name, getattr(o, name)) for name, value in inspect.getmembers(o.__class__, lambda p: isinstance(p, property))]
        values = [(name, value) for name, value in o.__dict__.items() if not name.startswith('_')]
        return dict(properties + values)

m = my_class()
print(json.dumps(m, cls=MyEncoder))

Prints:

{"foo": "Hello World!", "i": 111}
Andrej Kesely
  • 168,389
  • 15
  • 48
  • 91