How to return json in python graphene resolver without backslashes before quotation marks
Asked Answered
J

5

17

I have a backend server in python (Flask + Graphene) and I need to return a JSON object like this:

{
      's1': "Section 1",
      's2': "Section 2",
      's3': "Section 3",
      's4': "Section 4"
}

The resolver looks like below:

 questionnaire = graphene.types.json.JSONString(
        description='JSON result test')

    def resolve_questionnaire(self, info: graphql.ResolveInfo):
        sections = {
          's1': "Section 1",
          's2': "Section 2",
          's3': "Section 3",
          's4': "Section 4"
        }

        print(json.dumps(sections))
        return sections

and in console I see as a result of print(json.dumps(sections)) as I expect:

user-api_1  | {"s1": "Section 1", "s2": "Section 2", "s3": "Section 3", "s4": "Section 4"}

But in GraphiQL i see all quotation marks with backslashes: enter image description here

When I change the return sections to return json.dumps(sections) I get the result like this: enter image description here

The question is how to properly return a JSON object in graphene resolver? I know that there is json.replace method to use like here, but I believe that I am simply producing/passing object in wrong way.

Jaundice answered 14/8, 2018 at 9:21 Comment(0)
F
16

Your initial result of

{
  "data": {
    "questionnaire": "{\"s1\": \"Section 1\", \"s2\": \"Section 2\", \"s3\": \"Section 3\", \"s4\": \"Section 4\"}"
  }
}

is the intended behavior. After all, questionnaire resolves to a JSONString. Since it is a string it must be double quoted, thus its inner quotations must be escaped. This is according to JSON's standards.

To use that string you, would have to run some sort of JSON parser on the data.questionnaire object. In javascript, for instance, it would be something like:

var data;
// Fetching logic to get the data object from your GraphQL server
var sections = JSON.parse(data.questionaire);

// Now you can access its objects
console.log(sections.s1) // Should print "Section 1" on the dev console

However, the method described above is not ideal if the keys of sections are not predetermined (sections.s5 may be defined in one case but undefined in another). Instead, you might rather have an array that you can iterate over. To do this, you would have to define a "model" that has explicit key-value pairs. Doing this way is format suited for GraphQL, too. For instance:

import graphene

# Our new model
class Section(graphene.ObjectType):
    key = graphene.String()        # dictionary key
    header = graphene.String()     # dictionary value

# Your previous schema with modifications
class Query(graphene.ObjectType):
    # questionnaire = graphene.types.json.JSONString(description='JSON result test')

    # Return a list of section objects
    questionnaire = graphene.List(Section)

    def resolve_questionnaire(self, info: graphql.ResolveInfo):
        sections = {
          's1': "Section 1",
          's2': "Section 2",
          's3': "Section 3",
          's4': "Section 4"
        }

        sections_as_obj_list = [] # Used to return a list of Section types

        # Create a new Section object for each item and append it to list
        for key, value in sections.items(): # Use sections.iteritems() in Python2
            section = Section(key, value) # Creates a section object where key=key and header=value
            sections_as_obj_list.append(section)

        # return sections
        return sections_as_obj_list

Now, if we run the query:

query {
  questionnaire {
    key
    header
  }
}

It returns a JSON array that can be iterated through.

{
  "data" {
    "questionnaire": [
      {
        "key": "s1",
        "header": "Section 1"
      },
      {
        "key": "s2",
        "header": "Section 2"
      },
      {
        "key": "s3",
        "header": "Section 3"
      },
      {
        "key": "s4",
        "header": "Section 4"
      },
    ]
  }
}
Fabian answered 24/8, 2018 at 0:24 Comment(2)
Thanks, I got to the same point, that I need to construct a graphQL object built of graphene.Fields and use structured query to retrieve it. Initially I was hoping to get pure JSON, as prior to using graphQL I was reading directly from JSON file stored on the client side. So when I saw in graphiQL that graphQL result looks like JSON I was fooled and wanted to get it from the server. But from server are getting object which we are serialising to JSON, is that correct?Jaundice
I'm not really sure what you are asking. GraphQL returns standard JSON. The problem is that you cannot just put a pure JSON object as a child of any query. That would be counter to how GraphQL is designed. They somewhat allow you to still do this though with the JSONString method. You just have to go through the extra step of parsing again, as I explained in the first part of my answer. And yes, the object returned from the resolver is serialized to JSON. The graphene.type you specify in the questionaire definition determines how this is done.Fabian
P
11

Graphene now has a GenericScalar type for this.

from graphene.types import generic

...
errors = generic.GenericScalar()
Purloin answered 28/7, 2020 at 8:49 Comment(0)
R
5

You can just subclass the JSON type and replace the serialize method:

class Any(JSON):
    @staticmethod
    def serialize(dt):
        return dt

Then instead of

questionnaire = Field(JSON)

write

questionnaire = Field(Any)

Yes, this does break the strictly-typed spirit of GraphQL, but if that's what you want to do, there's how to do it. Note that this is an output-only hack — it won't allow you to accept arbitrary structures as arguments.

Rickie answered 10/9, 2019 at 19:39 Comment(0)
P
1

In my case I have the JSON column called (details).

from graphene.types.scalars import Scalar

class ObjectField(Scalar):
   ''' convert the Json String into Json '''
   @staticmethod
   def serialize(dt):
      return dt

   @staticmethod
   def parse_literal(node):
      return node.value

   @staticmethod
   def parse_value(value):
      return value


class CustomDiseaseFactNode(graphene.Node):
    class Meta:
       name = 'diseaseFactNode'
    @staticmethod #this class used to get the primary key object id
    def to_global_id(type, id):
      return id

Call the JSONScalar from your object class

class DiseaseFactNode(SQLAlchemyObjectType):
"""DiseaseFact node."""
class Meta:
    model = DiseaseFact
    interfaces = (CustomDiseaseFactNode,)

details = JSONScalar()
Pettifogger answered 17/7, 2020 at 9:21 Comment(0)
A
0

Another approach, to return an array of json / dictionary.

I was trying to return json from a model mixin property attribute.

Basically an any type ( note: loses the benefits of typing ), helped me to return json from a list of dictionaries:

import graphene
from graphene import relay, Scalar
from graphene_django import DjangoObjectType

class DictType(Scalar):

    @staticmethod
    def serialize(dt):
        return dt

    @staticmethod
    def parse_literal(node):
        return node

    @staticmethod
    def parse_value(value):
        return value

The node itself was based on a model which included a mixin:

class InvoiceFileNode(DjangoObjectType):
    signed_url = graphene.String()
    variable_files = graphene.List(of_type=DictType)

    class Meta:
        model = InvoiceFile
        interfaces = (relay.Node,)
        filter_fields = []
        only_fields = (
           "id",
           "created",
           "signed_url",
           "variable_files",
        )

    def resolve_signed_url(self, *args, **kwargs):
        # @property access from FileMixin
        return self.signed_url

    def resolve_openable_signed_url(self, *args, **kwargs):
        # @property access from FileMixin
        return self.openable_signed_url

The following mixin was what I was trying to get returned, but using the of_type of JSONString serialized the dictionary to a json string:

class FileMixin(object):

   @property
   def signed_url(self) -> str:
       return get_signed_url(self.file)

   @property
   def variable_files(self) -> str:
       sizes = []
       for s in [128, 240, 720]:
           item = {"width": s}
           urls = []
           for n in [1,2,3]:
               urls.append(get_sized_url(self.file, n))
           item["urls"] = urls
           sizes.append(item) 
       return sizes       


class InvoiceFile(Models, FileMixin):
    created = DateTimeField()
    file = CharField()


I was having issues with returning something like:

[{"width": 123, "stuff": [{"more": "stuff"}]}}

note

this will probably not work if the dictionary returned contains any sorts of functions, or objects, etc.

Amp answered 23/3, 2020 at 19:38 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.