Skip to content
This repository has been archived by the owner on Aug 19, 2023. It is now read-only.

Support for inner dataclasses #137

Open
richard-moss opened this issue Sep 25, 2020 · 2 comments
Open

Support for inner dataclasses #137

richard-moss opened this issue Sep 25, 2020 · 2 comments

Comments

@richard-moss
Copy link

richard-moss commented Sep 25, 2020

Background

I'm using multi-level dataclasses defined using composition to model a complex report type data model.
However, differently to most, I'm not defining all my member dataclasses all at a top level, but including their definition with the over top level dataclass, using inner classes. (see below for example of what I mean)

disclaimer ...I don't know json schema's very well at all. The end goal here is an easy way to define a complex data model in python and then have that used by (and understood by) a Typescript backend - hence the desire to export the schema for the model in json schema - which the TS backend can then make use of.

What I'm doing

So I'm using a conceptual model like this, where I have a class W that contains (and also defines) a type X, and X contains/defines an XX:

@dataclass
class W(JsonSchemaMixin):
    
    @dataclass
    class X(JsonSchemaMixin):
            
        @dataclass
        class XX(JsonSchemaMixin):
            t: str
             
        b: XX
            
    y: X
   
print(json.dumps(W.json_schema(), indent=2))

Using inner dataclasses in this way seems to work very well (and passes mypy etc) as a self contained alternative to defining X and XX at a top level.

Problem

Only minor, but the schema generated above looks like:

{
  "type": "object",
  "required": [
    "y"
  ],
  "properties": {
    "y": {
      "$ref": "#/definitions/X"
    }
  },
  "description": "W(y: __main__.W.X)",
  "$schema": "http://json-schema.org/draft-06/schema#",
  "definitions": {
    "X": {
      "type": "object",
      "required": [
        "b"
      ],
      "properties": {
        "b": {
          "$ref": "#/definitions/XX"
        }
      },
      "description": "X(b: __main__.W.X.XX)"
    },
    "XX": {
      "type": "object",
      "required": [
        "t"
      ],
      "properties": {
        "t": {
          "type": "string"
        }
      },
      "description": "XX(t: str)"
    }
  }
}

The minor problem is that in the above schema, XX is not in any way sub-classed to (if that concept even has any meaning in json schema's - sorry at my level of json schemas I don't know) to X.
If (it's valid python - even though probably a Bad Idea, and not what I'd use in my code) you had a sub-class with the same name: eg W.X.X - then you'd get a naming conflict in definitions

Question

Is there anything that can indicate in the schema the heirarchy of the inner classes?

...and sorry again if my newbie level of json schema understanding makes me not even realize json schema can't support a concept of inner classes/types to begin with

Note that find_type_hints correctly sorts things out in terms of which classes are in which:

get_type_hints(W.X)
{'b': __main__.W.X.XX}
@ZdenekM
Copy link

ZdenekM commented Sep 29, 2020

I faced a similar problem and solved it by changing __name__ of the inner classes dynamically before generating the schema. My dataclasses typically look like this:

@dataclass
class Event(JsonSchemaMixin):
    
    @dataclass
    class Data(JsonSchemaMixin):
            
        @dataclass
        class Inner(JsonSchemaMixin):
            t: str
             
        b: Inner
            
    y: Data

And this code https://github.com/robofit/arcor2/blob/master/src/python/arcor2_arserver/models.py#L15 turns the name of Data into EventData and Inner into EventDataInner in the schema. Not sure if it is the best approach but it serves the purpose :-) It would be nice if something like this could be done within dataclasses-jsonschema.

@richard-moss
Copy link
Author

Hey Zdeněk,

thanks for the reply.

Great to see someone else using inner dataclasses. Even with a bunch of googling I hadn't found anyone mention that as a pattern, even though it was IMO a good way to do things.

I like you're approach with the inner auto renaming. I'll see if it would work in my solution!

thanks again

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants