If you’re here, you’ve probably been working on getting that nice synthetic GraphQL working in Azure API Management, excited about the possibilities and building it bit by bit.
Until one day, you noticed that some of the data that returned didn’t quite look right. When you call your backend, your dates look right (in that supreme ISO8601 format) but when you call that GraphQL API, it returns some of those dates in a non-sensical American format! I mean, who puts months before days and then year?
Well, I had the same issue and spent a good part of my day breaking my head over this. It was maddening. Was my schema wrong? Was my API formatting DateTimes differently based on who/what was calling it? Did my colleague who started helping me expand parts of my API mess up?
Spoiler warning! If you just want the answer and not see my hilarious mocked data in the provided demo sample date, Azure APIM Synthetic GraphQL does not like it when resolvers retrieve arrays of objects. If that resolver is part of a larger object, your beautiful dates will get parsed and formatted by Azure.
Setting up the demo
With that out of the way, let me provide you with a sample GraphQL schema, some resolvers and we can see this bug in action!
type Character {
interests: String!
quote: Quote
quotes: [Quote!]
}
type SafeCharacter {
interests: String!
quote: Quote
quotes: [Quote!]
}
type Quote{
quote: String!
date: String!
}
type Query {
Character: Character
SafeCharacter: SafeCharacter
}
Save that schema above and create a GraphQL API in your APIM instance.
Now with the API deployed, we can add the resolvers to the schema. Click the plus next to the property on the schema and add the content of the following code blocks to their respective resolvers until the schema looks like the below screenshot:
Only the codeblocks need to be copied into the Resolver Policy input box, no need to
edit any other values.
QuoteResolver
GET
https://microsoft.com
@{
var firstQuote = new JObject();
firstQuote.Add("quote", "My ex-wife still misses me but her aim is getting better");
firstQuote.Add("date", "2012-06-29T00:00:00");
return firstQuote.ToString(Newtonsoft.Json.Formatting.None);
}
QuotesResolver
GET
https://microsoft.com
@{
var firstQuote = new JObject();
firstQuote.Add("quote", "My ex-wife still misses me but her aim is getting better");
firstQuote.Add("date", "2012-06-29T00:00:00");
var secondQuote = new JObject();
secondQuote.Add("quote", "Avenge me, kids! AVENGE ME");
secondQuote.Add("date", "2012-06-30T00:00:00");
var thirdQuote = new JObject();
thirdQuote.Add("quote", "HOT BELGIAN WAFFLES");
thirdQuote.Add("date", "2015-03-09T00:00:00");
var array = new JArray();
array.Add(firstQuote);
array.Add(secondQuote);
array.Add(thirdQuote);
return array.ToString();
}
SafeCharacterResolver
GET
https://microsoft.com
@{
var result = new JObject();
result.Add("interests", "Scamming money out of unknowing tourists. Also, gold.");
var firstQuote = new JObject();
firstQuote.Add("quote", "My ex-wife still misses me but her aim is getting better");
firstQuote.Add("date", "2012-06-29T00:00:00");
var secondQuote = new JObject();
secondQuote.Add("quote", "Avenge me, kids! AVENGE ME");
secondQuote.Add("date", "2012-06-30T00:00:00");
var thirdQuote = new JObject();
thirdQuote.Add("quote", "HOT BELGIAN WAFFLES");
thirdQuote.Add("date", "2015-03-09T00:00:00");
var array = new JArray();
array.Add(firstQuote);
array.Add(secondQuote);
array.Add(thirdQuote);
result.Add("quotes", array);
result.Add("quote", firstQuote);
return result.ToString(Newtonsoft.Json.Formatting.None);
}
CharacterResolver
GET
https://microsoft.com
@{
var result = new JObject();
result.Add("interests", "Scamming money out of unknowing tourists. Also, gold.");
return result.ToString(Newtonsoft.Json.Formatting.None);
}
These resolvers will make a call (because we can not just return data, sadly) but we don’t care about the return value. We will overwrite the body with some mocked data to show where the issue lies.
The Character resolver will simply return a Json object with the “interests” property set. The GraphQL API will then look if it has resolvers for the other properties that have not yet been provided and call those if available.
If everything was setup correctly, it will call the Quote and Quotes resolvers. Who also provides some mock data in the same way.
The SafeCharacters resolver will just provide the entire object we mocked with the above 3 resolvers in one go.
Actual demo
Before we test the queries, head over to the Resolvers tab and open the Character-quotes resolver. Click Run Test and you can see that the DateTimes are returned as expected.
Head over to the test tab and test the Character query in full.
Like magic, our GraphQL API has gained a new functionality with the accompanying bugs and it formats some of our DateTimes to an American format!
Try it again for the SafeCharacter and you’ll see that now our DateTimes are kept in their original format.
Conclusion
With this demo, it is clear that when an object is put together from different resolvers, issues can arise that are not caused by the data itself.
Resolvers that return an array of objects are sadly not reliable and should be wrapped in another object to prevent your data from being transformed. I am currently still looking into it, hoping to find a fix or another way to circumvent this issue; if I find anything, this post will be updated!
If you’ve made it to here, thanks for reading and I hoped this helped you!
Have a great one!
– Jochim Vandooren