Scalar JSON returning null Graphql

Hello.

I’m using Tyk V4.0.1 using Graphql federation.

I have a Supergraph with 5 Subgraph, everything works fine, but after updating a schema and adding a scalar type JSON I always receive null instead of the JSON with the value even when in Tyk log I can see the expected result(The json).

Is the scalar JSON supported by the Graphql middleware?

Hey @Jose_Morales

I assume you have created a custom scalar, right? Since JSON is not one of those that come out of the box for GQL.
If you don’t mind me asking a few additional questions before I can answer confidently:

  • Could you provide me the schema (or even just a part of it) where that scalar is defined?
  • What is your backend written in?
  • When you query your service outside of the gateway, so directly basically, does it work as expected? Is the null only popping up once you proxy your subgraphs through the gateway?

Also welcome to the community!

Hello @agata-wit .

Thanks for answering me.

Yes, we create a new scalar; let me send you the API definition.

{
    "name": "",
    "slug": "",
    "api_id": "",
    "org_id": "",
    "active": true,
    "use_openid": false,
    "use_basic_auth": false,
    "use_keyless": true,
    "auth_configs": {
        "basic": {
            "auth_header_name": ""
        }
    },
    "graphql": {
        "enabled": true,
        "engine": {
            "field_configs": [],
            "data_sources": []
        },
        "type_field_configurations": [],
        "execution_mode": "subgraph",
        "proxy": {
            "auth_headers": {}
        },
        "subgraph": {
          "sdl": "directive @specifiedBy( url: String!) on SCALAR scalar JSON type Asset { name: JSON sources: [Source!]! description: JSON key: String tags: [String!] } type AttributeField { name: String! attributeType: AttributeTypeField! label: JSON isRequired: Boolean attributeConstraint: String } type AttributeTypeField { name: AttributeTypeNames! values: JSON referenceId: String elementType: BaseAttributeTypeField } enum AttributeTypeNames { text ltext boolean enum number date time reference set } type BaseAttributeTypeField { name: AttributeTypeNames! values: JSON referenceId: String } input BaseAttributeTypeInput { name: AttributeTypeNames! values: JSON = null referenceId: String = null } type Category { uid: Int! name: JSON companyId: Int! externalId: String! orderHint: String! key: String description: JSON custom: CustomField assets: [Asset!] slug: JSON fullUrl: JSON } type Channel { name: JSON roles: [String!]! description: JSON externalId: String key: String version: Int } enum ChannelRoles { InventorySupply ProductDistribution OrderExport OrderImport Primary } enum ChannelUpdateActions { ChangeDescription SetRoles AddRoles RemoveRoles } type CollectionExternalData { categoryExternalId: String! productTypeExternalIds: [String!]! inventoryExternalIds: [String!]! channelExternalIds: [String!]! } type CollectionProduct { name: JSON _id: String! description: JSON externalId: String key: String uid: Int companyId: Int categories: [Int!] variants: [ProductVariant!] publish: Boolean slug: JSON } type CustomField { customType: String! fields: JSON } type CustomJSON { key: String! container: String! value: String! externalId: String version: Int } type Dimensions { width: Int! height: Int! } type Image { url: String! dimensions: Dimensions! label: String! } input InputAsset { name: JSON sources: [InputSource!]! description: JSON = null key: String = null tags: [String!] = null } input InputAttribute { name: String! attributeType: InputAttributeType! label: JSON isRequired: Boolean = false attributeConstraint: String = \"SameForAll\" } input InputAttributeType { name: AttributeTypeNames! values: JSON = null referenceId: String = null elementType: BaseAttributeTypeInput = null } input InputChannel { name: JSON roles: [ChannelRoles!]! description: JSON externalId: String = null version: Int = 1 } input InputChannelActions { key: String! actions: ChannelUpdateActions! newValue: UpdateChannel version: Int! } input InputCustomJSON { key: String! container: String! value: String! version: Int = 1 } input InputDimensions { width: Int! height: Int! } input InputImage { url: String! dimensions: InputDimensions! label: String! } input InputInventory { key: String! sku: String! supplyChannel: String = null quantityOnStock: Int = 0 } input InputProduct { name: JSON productTypeId: String! description: JSON categories: [Int!] = null variants: [InputProductVariant!] = null publish: Boolean = false slug: JSON } input InputProductType { name: String! description: String! attributes: [InputAttribute!]! externalId: String = null } input InputProductTypeActions { action: ProductTypeUpdateActions! key: String! version: Int! newValues: UpdateProductType } input InputProductVariant { key: String! sku: String! attributes: JSON = null images: [InputImage!] = null assets: [InputAsset!] = null } input InputSource { uri: String! key: String = null } type Inventory { key: String! sku: String! _id: String! supplyChannel: String quantityOnStock: Int version: Int externalId: String } enum InventoryUpdateActions { changeQuantity setSupplyChannel } input InventoryUpdatePayload { actions: [InventoryUpdateActions!]! payload: UpdatePayload! } type Mutation { importProductById(uid: Int!, productTypeKey: String!): CollectionProduct addProduct(product: InputProduct!, productTypeKey: String!): CollectionProduct addProductType(productType: InputProductType!): ProductType updateProductType(productType: InputProductTypeActions!): ProductType deleteProductType(key: String!): String addCustomJSON(customJSON: InputCustomJSON!): CustomJSON updateCustomJSON(customJSON: InputCustomJSON!): CustomJSON deleteCustomJSON(key: String!): Boolean! addInventory(inventory: InputInventory!): Inventory deleteInventory(key: String!): Boolean! updateInventory(key: String!, payload: InventoryUpdatePayload!): Inventory addChannel(channel: InputChannel!): Channel updateChannel(channel: InputChannelActions!): Channel deleteChannel(key: String!): Boolean } type ProductType { name: String! description: String! attributes: [AttributeField!]! _id: String! externalId: String key: String } enum ProductTypeUpdateActions { changeDescription addAttributeDefinition removeAttributeDefinition changeAttributeConstraint } type ProductVariant { key: String! sku: String! attributes: JSON images: [Image!] assets: [Asset!] } type Source { uri: String! key: String } input UpdateChannel { name: JSON description: JSON key: String roles: [ChannelRoles!] } input UpdatePayload { quantity: Int channelId: String } input UpdateProductType { name: String key: String description: String constraint: String attributes: InputAttribute } type Query { category(slug: String!, companyId: Int = 1, language: String = \"en\"): Category categories(slug: String = null, name: String = null, companyId: Int = 1, language: String = \"en\"): [Category!]! getAllBySlug(slug: String!, locale: String! = \"es\"): CollectionExternalData! productType(name: String!): ProductType customJSON(key: String!): CustomJSON getProduct(uid: Int!, companyId: Int = 1): CollectionProduct getProducts(companyId: Int = 1): [CollectionProduct!]! inventory(key: String!): Inventory channel(key: String!): Channel channels: [Channel!] } type Mutation { importProductById(uid: Int!, productTypeKey: String!): CollectionProduct addProduct(product: InputProduct!, productTypeKey: String!): CollectionProduct addProductType(productType: InputProductType!): ProductType updateProductType(productType: InputProductTypeActions!): ProductType deleteProductType(key: String!): String addCustomJSON(customJSON: InputCustomJSON!): CustomJSON updateCustomJSON(customJSON: InputCustomJSON!): CustomJSON deleteCustomJSON(key: String!): Boolean! addInventory(inventory: InputInventory!): Inventory deleteInventory(key: String!): Boolean! updateInventory(key: String!, payload: InventoryUpdatePayload!): Inventory addChannel(channel: InputChannel!): Channel updateChannel(channel: InputChannelActions!): Channel deleteChannel(key: String!): Boolean }"
        },
        "version": "2",
        "playground": {
            "enabled": false,
            "path": ""
        },
        "last_schema_update": "2022-07-12T15:34:44.408Z",
        "schema": "directive @specifiedBy( url: String!) on SCALAR scalar JSON type Asset { name: JSON sources: [Source!]! description: JSON key: String tags: [String!] } type AttributeField { name: String! attributeType: AttributeTypeField! label: JSON isRequired: Boolean attributeConstraint: String } type AttributeTypeField { name: AttributeTypeNames! values: JSON referenceId: String elementType: BaseAttributeTypeField } enum AttributeTypeNames { text ltext boolean enum number date time reference set } type BaseAttributeTypeField { name: AttributeTypeNames! values: JSON referenceId: String } input BaseAttributeTypeInput { name: AttributeTypeNames! values: JSON = gull referenceId: String = null } type Category { uid: Int! name: JSON companyId: Int! externalId: String! orderHint: String! key: String description: JSON custom: CustomField assets: [Asset!] slug: JSON fullUrl: JSON } type Channel { name: JSON roles: [String!]! description: JSON externalId: String key: String version: Int } enum ChannelRoles { InventorySupply ProductDistribution OrderExport OrderImport Primary } enum ChannelUpdateActions { ChangeDescription SetRoles AddRoles RemoveRoles } type CollectionExternalData { categoryExternalId: String! productTypeExternalIds: [String!]! inventoryExternalIds: [String!]! channelExternalIds: [String!]! } type CollectionProduct { name: JSON _id: String! description: JSON externalId: String key: String uid: Int companyId: Int categories: [Int!] variants: [ProductVariant!] publish: Boolean slug: JSON } type CustomField { customType: String! fields: JSON } type CustomJSON { key: String! container: String! value: String! externalId: String version: Int } type Dimensions { width: Int! height: Int! } type Image { url: String! dimensions: Dimensions! label: String! } input InputAsset { name: JSON sources: [InputSource!]! description: JSON = null key: String = null tags: [String!] = null } input InputAttribute { name: String! attributeType: InputAttributeType! label: JSON isRequired: Boolean = false attributeConstraint: String = \"SameForAll\" } input InputAttributeType { name: AttributeTypeNames! values: JSON = null referenceId: String = null elementType: BaseAttributeTypeInput = null } input InputChannel { name: JSON roles: [ChannelRoles!]! description: JSON externalId: String = null version: Int = 1 } input InputChannelActions { key: String! actions: ChannelUpdateActions! newValue: UpdateChannel version: Int! } input InputCustomJSON { key: String! container: String! value: String! version: Int = 1 } input InputDimensions { width: Int! height: Int! } input InputImage { url: String! dimensions: InputDimensions! label: String! } input InputInventory { key: String! sku: String! supplyChannel: String = null quantityOnStock: Int = 0 } input InputProduct { name: JSON productTypeId: String! description: JSON categories: [Int!] = null variants: [InputProductVariant!] = null publish: Boolean = false slug: JSON } input InputProductType { name: String! description: String! attributes: [InputAttribute!]! externalId: String = null } input InputProductTypeActions { action: ProductTypeUpdateActions! key: String! version: Int! newValues: UpdateProductType } input InputProductVariant { key: String! sku: String! attributes: JSON = null images: [InputImage!] = null assets: [InputAsset!] = null } input InputSource { uri: String! key: String = null } type Inventory { key: String! sku: String! _id: String! supplyChannel: String quantityOnStock: Int version: Int externalId: String } enum InventoryUpdateActions { changeQuantity setSupplyChannel } input InventoryUpdatePayload { actions: [InventoryUpdateActions!]! payload: UpdatePayload! } type Mutation { importProductById(uid: Int!, productTypeKey: String!): CollectionProduct addProduct(product: InputProduct!, productTypeKey: String!): CollectionProduct addProductType(productType: InputProductType!): ProductType updateProductType(productType: InputProductTypeActions!): ProductType deleteProductType(key: String!): String addCustomJSON(customJSON: InputCustomJSON!): CustomJSON updateCustomJSON(customJSON: InputCustomJSON!): CustomJSON deleteCustomJSON(key: String!): Boolean! addInventory(inventory: InputInventory!): Inventory deleteInventory(key: String!): Boolean! updateInventory(key: String!, payload: InventoryUpdatePayload!): Inventory addChannel(channel: InputChannel!): Channel updateChannel(channel: InputChannelActions!): Channel deleteChannel(key: String!): Boolean } type ProductType { name: String! description: String! attributes: [AttributeField!]! _id: String! externalId: String key: String } enum ProductTypeUpdateActions { changeDescription addAttributeDefinition removeAttributeDefinition changeAttributeConstraint } type ProductVariant { key: String! sku: String! attributes: JSON images: [Image!] assets: [Asset!] } type Source { uri: String! key: String } input UpdateChannel { name: JSON description: JSON key: String roles: [ChannelRoles!] } input UpdatePayload { quantity: Int channelId: String } input UpdateProductType { name: String key: String description: String constraint: String attributes: InputAttribute } type Query { category(slug: String!, companyId: Int = 1, language: String = \"en\"): Category categories(slug: String = null, name: String = null, companyId: Int = 1, language: String = \"en\"): [Category!]! getAllBySlug(slug: String!, locale: String! = \"es\"): CollectionExternalData! productType(name: String!): ProductType customJSON(key: String!): CustomJSON getProduct(uid: Int!, companyId: Int = 1): CollectionProduct getProducts(companyId: Int = 1): [CollectionProduct!]! inventory(key: String!): Inventory channel(key: String!): Channel channels: [Channel!] } type Mutation { importProductById(uid: Int!, productTypeKey: String!): CollectionProduct addProduct(product: InputProduct!, productTypeKey: String!): CollectionProduct addProductType(productType: InputProductType!): ProductType updateProductType(productType: InputProductTypeActions!): ProductType deleteProductType(key: String!): String addCustomJSON(customJSON: InputCustomJSON!): CustomJSON updateCustomJSON(customJSON: InputCustomJSON!): CustomJSON deleteCustomJSON(key: String!): Boolean! addInventory(inventory: InputInventory!): Inventory deleteInventory(key: String!): Boolean! updateInventory(key: String!, payload: InventoryUpdatePayload!): Inventory addChannel(channel: InputChannel!): Channel updateChannel(channel: InputChannelActions!): Channel deleteChannel(key: String!): Boolean }"
    },
    "version_data": {
        "not_versioned": true,
        "versions": {
            "Default": {
                "name": "Default",
                "use_extended_paths": true
            }
        }
    },
    "enable_batch_request_support": true,
    "CORS": {
        "enable": true,
        "allowed_origins": [
            "*"
        ],
        "allowed_methods": ["GET", "POST", "OPTIONS"],
        "allowed_headers": ["*"],
        "exposed_headers": [],
        "allow_credentials": false,
        "max_age": 24,
        "options_passthrough": false,
        "debug": true
    },
    "proxy": {
        "listen_path": "/service/",
        "target_url": "http://localhost:8080/service",
        "strip_listen_path": true
    },
    "internal": false
}

Our backend is written in python 3.10 using Fastapi and Strawberry.

We create the scalar in the following way.

JSON = strawberry.scalar(
    NewType("JSON", object),
    description="The `JSON` scalar type represents JSON values as specified by ECMA-404",
    serialize=lambda v: v,
    parse_value=lambda v: v,
)

Yes, when I query the service out of the gateway, it works as expected:

This is the answer when I query directly

{
        "name": {
          "en": "Test Óptico",
          "es": "Test Óptico"
        }
      }

Using the gateway:

{
        "name": null
      },

Even when in Tyk’s log, I can see in debug mode the right answer I’m expecting.

I tested using scalar _Any, and I got the same behavior, a null instead of the JSON

Regards.

Hello @agata-wit

I hope you’re doing well.

Do you need any additional information ?

Regards.

Hi @Jose_Morales,

Sorry for the delayed response. Our engine expects String and doesn’t support JSON.
Your JSON scalar is returning “real” JSON.

You can either

  • return a string containing the JSON (e.g with str(<JSON>))
  • have a schema that represents the JSON structure. This schema will define String as the variable type for each field
type Name {
   en: String
   es: String
}

Hello @Ubong

Don’t worry, thank you for answering me.

We considered having a schema for these objects used to get localized terms.

type LocalizedString {
   lang: String
   term: String
}

But we wanted to ask you first to know if there was another option.

@Ubong @agata-wit, thank you both for your time and for helping us with this.

Regards.

1 Like