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

John Overiron picture John Overiron · Aug 14, 2018 · Viewed 7.2k times · Source

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.

Answer

Tobe E picture Tobe E · Aug 24, 2018

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"
      },
    ]
  }
}