title: What is the recommended way to include properties in dataclasses in asdict or serialization?
body: <p>Note this is similar to <a href="https://stackoverflow.com/questions/51733882/how-to-get-property-methods-in-asdict">How to get @property methods in asdict?</a>.</p>
  <p>I have a (frozen) nested data structure like the following. A few properties that are (purely) dependent on the fields are defined.</p>
  <pre><code>import copy
  import dataclasses
  import json
  from dataclasses import dataclass
  
  @dataclass(frozen=True)
  class Bar:
      x: int
      y: int
  
      @property
      def z(self):
          return self.x + self.y
  
  @dataclass(frozen=True)
  class Foo:
      a: int
      b: Bar
  
      @property
      def c(self):
          return self.a + self.b.x - self.b.y
  </code></pre>
  <p>I can serialize the data structure as follows:</p>
  <pre><code>class CustomEncoder(json.JSONEncoder):
      def default(self, o):
          if dataclasses and dataclasses.is_dataclass(o):
              return dataclasses.asdict(o)
          return json.JSONEncoder.default(self, o)
  
  foo = Foo(1, Bar(2,3))
  print(json.dumps(foo, cls=CustomEncoder))
  
  # Outputs {&quot;a&quot;: 1, &quot;b&quot;: {&quot;x&quot;: 2, &quot;y&quot;: 3}}
  </code></pre>
  <p>However, I would like to also serialize the properties (<code>@property</code>). Note I do not want to turn the properties into fields using <code>__post_init__</code> as I would like to keep the dataclass' frozen. <a href="https://stackoverflow.com/q/57893902/2750819">I do not want to use <code>obj.__setattr__</code> to work around the frozen fields.</a> I also do not want to pre-compute the values of the properties outside the class and pass them in as fields.</p>
  <p>The current solution I am using is to explicitly write out how each object is serialized as follows:</p>
  <pre><code>class CustomEncoder2(json.JSONEncoder):
      def default(self, o):
          if isinstance(o, Foo):
              return {
                  &quot;a&quot;: o.a,
                  &quot;b&quot;: o.b,
                  &quot;c&quot;: o.c
              }
          elif isinstance(o, Bar):
              return {
                  &quot;x&quot;: o.x,
                  &quot;y&quot;: o.y,
                  &quot;z&quot;: o.z
              }
          return json.JSONEncoder.default(self, o)
  
  foo = Foo(1, Bar(2,3))
  print(json.dumps(foo, cls=CustomEncoder2))
  
  # Outputs {&quot;a&quot;: 1, &quot;b&quot;: {&quot;x&quot;: 2, &quot;y&quot;: 3, &quot;z&quot;: 5}, &quot;c&quot;: 0} as desired
  </code></pre>
  <p>For a few levels of nesting, this is manageable but I am hoping for a more general solution. For example, here is a (hacky) solution that monkey-patches the _asdict_inner implementation from the dataclasses library.</p>
  <pre><code>def custom_asdict_inner(obj, dict_factory):
      if dataclasses._is_dataclass_instance(obj):
          result = []
          for f in dataclasses.fields(obj):
              value = custom_asdict_inner(getattr(obj, f.name), dict_factory)
              result.append((f.name, value))
          # Inject this one-line change
          result += [(prop, custom_asdict_inner(getattr(obj, prop), dict_factory)) for prop in dir(obj) if not prop.startswith('__')]
          return dict_factory(result)
      elif isinstance(obj, tuple) and hasattr(obj, '_fields'):
          return type(obj)(*[custom_asdict_inner(v, dict_factory) for v in obj])
      elif isinstance(obj, (list, tuple)):
          return type(obj)(custom_asdict_inner(v, dict_factory) for v in obj)
      elif isinstance(obj, dict):
          return type(obj)((custom_asdict_inner(k, dict_factory),
                            custom_asdict_inner(v, dict_factory))
                           for k, v in obj.items())
      else:
          return copy.deepcopy(obj)
  
  dataclasses._asdict_inner = custom_asdict_inner
  
  class CustomEncoder3(json.JSONEncoder):
      def default(self, o):
          if dataclasses and dataclasses.is_dataclass(o):
              return dataclasses.asdict(o)
          return json.JSONEncoder.default(self, o)
  
  foo = Foo(1, Bar(2,3))
  print(json.dumps(foo, cls=CustomEncoder3))
  
  # Outputs {&quot;a&quot;: 1, &quot;b&quot;: {&quot;x&quot;: 2, &quot;y&quot;: 3, &quot;z&quot;: 5}, &quot;c&quot;: 0} as desired
  </code></pre>
  <p>Is there a recommended way to achieve what I am trying to do?</p>