JSONField Models in Graphene Django
This article talks about how to return JSON in Python Graphene resolver without backslashes and quotation marks
Graphene is very stable and is probably the best library for creating GraphQL endpoints in Python. While working with Graphene, I came across an issue where JSONFields
were always returned with quotation marks and backslashes — JSONString
.

meta
field is returned as a JSON string rather than a JSON objectThis article aims to address this issue using GenericScalar, which is currently an undocumented type in the current version of Graphene documentation.
TL;DR
This can be easily solved by overriding your meta
field type in your DjangoObjectType
to GenericScalar
such as below.
from graphene.relay import Node
from graphene.types.generic import GenericScalar # Solution
from graphene_django import DjangoObjectType
from graphql_example.utils import CountableConnectionBase
from .filters import PersonFilter
from .models import Person
class Person(DjangoObjectType):
meta = GenericScalar() # Solution
class Meta:
model = Person
interfaces = (Node,)
filterset_class = PersonFilter
Detailed Example with Code
In this section, I am going to run through a few snippets of codes for creating a simple mutation and query that we can work with.

Database Model
Simple database model for demonstration.
from django.db import models
class Person(models.Model):
first_name = models.CharField(max_length=30, help_text='First name of the person.')
last_name = models.CharField(max_length=30, help_text='Last name of the person.')
metadata = models.JSONField(default=dict, blank=True, help_text='Metadata of the person.')
GraphQL Types
Creating a DjangoObjectType
for our Person
object.
from graphene.relay import Node
from graphene.types.generic import GenericScalar # Solution
from graphene_django import DjangoObjectType
from graphql_example.utils import CountableConnectionBase
from .filters import PersonFilter
from .models import Person
class Person(DjangoObjectType):
meta = GenericScalar() # Solution
class Meta:
model = Person
interfaces = (Node,)
filterset_class = PersonFilter
Django Filters
Let’s add some basic filters to our code while we are at it.
from django_filters import FilterSet, OrderingFilter
from .models import Person
class PersonFilter(FilterSet):
class Meta:
model = Person
fields = ['first_name', 'last_name']
order_by = OrderingFilter(
fields=(
('first_name'),
('last_name'),
)
)
GraphQL Mutations
For simplicity’s sake, we will only need a single createPerson
mutation for this example. We are going to pass in metadata
as a dictionary/JSON
data input for our mutation.
from graphene import ClientIDMutation, Field, String
from graphene.types.generic import GenericScalar
from .models import Person
from .types import PersonNode
class CreatePerson(ClientIDMutation):
person = Field(PersonNode)
class Input:
first_name = String(required=True)
last_name = String(required=True)
metadata = GenericScalar()
@classmethod
def mutate_and_get_payload(cls, _, info, **input):
first_name = input['first_name']
last_name = input['last_name']
metadata = input.get('metadata', {})
person = Person.objects.create(
first_name=first_name,
last_name=last_name,
metadata=metadata,
)
return CreatePerson(person=person)
GraphQL Schema
Finally, we will stitch everything together inside schema.py
and we are ready to try things out!
from graphene import ObjectType
from graphene.relay import Node
from graphene_django.filter import DjangoFilterConnectionField
from .mutations import CreatePerson
from .types import PersonNode
class Mutation(ObjectType):
create_person = CreatePerson.Field(description='Create a single Person.')
class Query(ObjectType):
person = Node.Field(PersonNode, description='Get a single Person detail.')
persons = DjangoFilterConnectionField(PersonNode, description='Return Person connection with pagination information.')
Results
Trying out the mutation
Let’s create a Person
object using our newly created CreatePerson
mutation at your /graphql
endpoint using the built-in GraphiQL IDE or any API client of your choice (Postman, Insomnia REST Client, etc.)
# Create a Person object
mutation createPerson($input: CreatePersonInput!) {
createPerson(input: $input) {
person {
id
}
}
}
# Query Variable
{
"input": {
"firstName": "Albert",
"lastName": "Joe",
"metadata": {
"is_admin": true,
"email": '[email protected]'
}
}
}
Final Solution
As you can see in the meta
field below, it’s returned as a JSONString
which is not what we wanted. Things could get uglier especially when you’re dealing with a large JSON
object.
{
"data": {
"createPerson": {
"person": {
"id": "VXNlck5vZGU6MTI0NDE4",
"firstName": "Albert",
"lastName": "Joe",
"meta": "{\"is_admin\": true, \"email\": \"[email protected]\"}"
}
}
}
}
The issue was apparently caused by the fact that JSONFields
are by default treated as JSONString
in Graphene.
However, this can simply be fixed by applying GenericScalar to types.py
such as below.
from graphene.relay import Node
from graphene.types.generic import GenericScalar # Solution
from graphene_django import DjangoObjectType
from graphql_example.utils import CountableConnectionBase
from .filters import PersonFilter
from .models import Person
class Person(DjangoObjectType):
meta = GenericScalar() # Solution
class Meta:
model = Person
interfaces = (Node,)
filterset_class = PersonFilter