Skip to content

stretch4x4/marshmallow-jsonschema

 
 

Repository files navigation

marshmallow-jsonschema

NOTE

This is a maintained refactor of the original marshmallow-jsonschema project.

The main goals of this refactor were:

  • Fix some long standing bugs
  • Support newer versions of Python
  • Publish a more recent release
  • Modernise builds and tooling to enable active development and future work

Overview

Build Status CodeQL OSSAR Ruff PyPI - Version GitHub Release

marshmallow-jsonschema translates marshmallow schemas into JSON Schema Draft v7 compliant jsonschema. See http://json-schema.org/

Why would I want my schema translated to JSON?

What are the use cases for this? Let's say you have a marshmallow schema in python, but you want to render your schema as a form in another system (for example: a web browser or mobile device).

Installation

Requires python>=3.10 and marshmallow>=3.11. (For python 2 & marshmallow 2 support, please use marshmallow-jsonschema<0.11)

pip install marshmallow-jsonschema

Some Client tools can render forms using JSON Schema

Examples

Simple Example

from marshmallow import Schema, fields
from marshmallow_jsonschema import JSONSchema

class UserSchema(Schema):
    username = fields.String()
    age = fields.Integer()
    birthday = fields.Date()

user_schema = UserSchema()

json_schema = JSONSchema()
json_schema.dump(user_schema)

Yields:

{'properties': {'age': {'format': 'integer',
                        'title': 'age',
                        'type': 'number'},
                'birthday': {'format': 'date',
                             'title': 'birthday',
                             'type': 'string'},
                'username': {'title': 'username', 'type': 'string'}},
 'required': [],
 'type': 'object'}

Nested Example

from marshmallow import Schema, fields
from marshmallow_jsonschema import JSONSchema
from tests import UserSchema


class Athlete(object):
    user_schema = UserSchema()

    def __init__(self):
        self.name = 'sam'


class AthleteSchema(Schema):
    user_schema = fields.Nested(JSONSchema)
    name = fields.String()


athlete = Athlete()
athlete_schema = AthleteSchema()

athlete_schema.dump(athlete)

Complete example Flask application using brutisin/json-forms

Screenshot

This example renders a form not dissimilar to how wtforms might render a form.

However rather than rendering the form in python, the JSON Schema is rendered using the javascript library brutusin/json-forms.

from flask import Flask, jsonify
from marshmallow import Schema, fields
from marshmallow_jsonschema import JSONSchema

app = Flask(__name__)


class UserSchema(Schema):
    name = fields.String()
    address = fields.String()


@app.route('/schema')
def schema():
    schema = UserSchema()
    return jsonify(JSONSchema().dump(schema))


@app.route('/')
def home():
    return '''<!DOCTYPE html>
<head>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/brutusin.json-forms/1.3.0/css/brutusin-json-forms.css"><Paste>
<script src="https://code.jquery.com/jquery-1.12.1.min.js" integrity="sha256-I1nTg78tSrZev3kjvfdM5A5Ak/blglGzlaZANLPDl3I=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.string/3.3.4/underscore.string.min.js"></script>
<script src="https://cdn.jsdelivr.net/brutusin.json-forms/1.3.0/js/brutusin-json-forms.min.js"></script>
<script>
$(document).ready(function() {
    $.ajax({
        url: '/schema'
        , success: function(data) {
            var container = document.getElementById('myform');
            var BrutusinForms = brutusin["json-forms"];
            var bf = BrutusinForms.create(data);
            bf.render(container);
        }
    });
});
</script>
</head>
<body>
<div id="myform"></div>
</body>
</html>
'''


if __name__ == '__main__':
    app.run(host='0.0.0.0', debug=True)

Advanced usage

Custom Type support

For custom field classes, you can add a mapping to an equivalent python type using the 'jsonschema_python_type' key inside metadata. This allows the library to attempt to populate schema values as if it were an instance of a Field with equivalence to that python type.

If requiring proper schema validation and nesting with custom list-like fields, consider subclassing fields.List, or alternatively provide a self.inner attribute, set to a field instance representing the type of the items inside the custom list.

Example custom field definitions, using 'jsonschema_python_type':

class Colour(fields.Field):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.metadata["jsonschema_python_type"] = str

    def _serialize(self, value, _attr, _obj):
        r, g, b = value
        return f"#{r:x}{g:x}{b:x}"

class ColourList(fields.Field):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.metadata["jsonschema_python_type"] = list
        self.inner = Colour()

class FlexibleType(fields.Field):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        print(f"This is field treated like a {self.metadata['jsonschema_python_type']}")

class UserSchema(Schema):
        favourite_colour = Colour()
        close_favourites = ColourList()
        bool_field = FlexibleType(metadata={"jsonschema_python_type": bool})
        int_field = FlexibleType(metadata={"jsonschema_python_type": int})

schema = UserSchema()
json_schema = JSONSchema()
json_schema.dump(schema)

As an alternative option, you can also supply an equivalent Field type here instead. For example "{jsonschema_python_type": fields.String}.

[deprecated] Custom Type support using _jsonschema_type_mapping()

If desiring strict control over the output schema for a custom field, add a _jsonschema_type_mapping method to your field to supply your own field schema. Whatever is put here will be exactly what gets serialized to JSON Schema.

Note that no automated nesting logic or validation will be applied for a schema given this way.

A common use case for this is creating a dropdown menu using enum (see Gender below).

class Colour(fields.Field):

    def _jsonschema_type_mapping(self):
        return {
            'type': 'string',
        }

    def _serialize(self, value, _attr, _obj):
        r, g, b = value
        return f"#{r:x}{g:x}{b:x}"

class Gender(fields.String):
    def _jsonschema_type_mapping(self):
        return {
            'type': 'string',
            'enum': ['Male', 'Female']
        }


class UserSchema(Schema):
    name = fields.String(required=True)
    favourite_colour = Colour()
    gender = Gender()

schema = UserSchema()
json_schema = JSONSchema()
json_schema.dump(schema)

An alternative use of the _jsonschema_type_mapping() method is unlocked by providing the "generate_missing_schema_keys" key, with an equivalent python type as the value. This allows the library to attempt to automatically generate the values for missing keys it can extract from the field, given the equivalent type.

_jsonschema_type_mapping() is deprecated in favour of using 'jsonschema_python_type' for type aliases, as described above.

React-JSONSchema-Form Extension

react-jsonschema-form is a library for rendering jsonschemas as a form using React. It is very powerful and full featured.. the catch is that it requires a proprietary uiSchema to provide advanced control how the form is rendered. Here's a live playground

(new in version 0.10.0)

from marshmallow_jsonschema.extensions import ReactJsonSchemaFormJSONSchema

class MySchema(Schema):
    first_name = fields.String(
        metadata={
            'ui:autofocus': True,
        }
    )
    last_name = fields.String()

    class Meta:
        react_uischema_extra = {
            'ui:order': [
                'first_name',
                'last_name',
            ]
        }


json_schema_obj = ReactJsonSchemaFormJSONSchema()
schema = MySchema()

# here's your jsonschema
data = json_schema_obj.dump(schema)

# ..and here's your uiSchema!
ui_schema_json = json_schema_obj.dump_uischema(schema)