Rewrite JsonQueryResultSerializer to not serialize to string
See original GitHub issueIntroduction
Currently our JSON query result serializer uses Json.NET and serializes the result to a string and then writes that into a byte stream.
In order to save memory and improve performance we want to build a specialized serializer that only serializes IQueryResult
objects and writes the JSON directly into the byte stream without the need of manifesting the JSON first as .NET string
object.
Result Object
The IQueryResult
represents the execution result of a GraphQL Query
or Mutation
. The GraphQL Result structure is detailed on more detail in the GraphQL Spec (Response Format).
Our serializer IQueryResultSerializer
has one public method SerializeAsync(IReadOnlyQueryResult result, Stream stream)
.
The result object is a read-only result and has the following structure:
public interface IExecutionResult
{
IReadOnlyCollection<IError> Errors { get; }
IReadOnlyDictionary<string, object> Extensions { get; }
}
public interface IReadOnlyQueryResult
: IExecutionResult
{
IReadOnlyDictionary<string, object> Data { get; }
}
Data
The data property represents the query result and contains the data that was requested. The data dictionary is ordered correctly, so the serializer does not have to worry puting entries into the correct order.
Spec: Serialized Map Ordering
Since the result of evaluating a selection set is ordered, the serialized Map of results should preserve this order by writing the map entries in the same order as those fields were requested as defined by query execution.
The data property can be empty but will never be null. If the data property is empty than the serializer should NOT write a data entry.
The data property represents a graph. The graph consists of the following three types:
-
Objects Objects are represented as
IReadOnlyDictionary<string, object>
and each dictionary is guarnteed to be orderd as descriped in theSerialized Map Ordering
-section. -
Lists Lists are represented as
IReadOnlyList<object>
-
Scalars and Enums All scalars will serialize to simple .NET value types.
Apart from .NET value types we also have to handle enum types here as a special case.
In our first implementation we should look more at the performance site and not think to much about exensibility here.
Extensions
The extension property is organized like the data property. So, we can handle this one the same way. The extension map is reserved for custom extensions like the Apollo Tracing Protocol
.
Errors
The errors property is a collection of IError
. If this collection is empty it should not be written into the output. Since, IError
is a fixed structure appart from the Extensions
-property we should be able to write a very simple logic with this one.
Root Property Order
If the error collection has any entry than we will serialize the error property first and the data property second.
Spec Recomendation:
When errors is present in the response, it may be helpful for it to appear first when serialized to make it more clear when errors are present in a response during debugging.
When there is no error than the data property has to be serialized first.
The extension property should always come last.
Implementation
Performance
We do not want to use Linq for this one. So, if we want to check if something is empty use Count. If we need some algorithms we will implement them classically without the need for IEnumerable
. So, basicallay for(int i …).
We will need to put some constants asside. Insead of putting the char for braces aside we should do something like the following:
private const byte _leftBrace = (byte)'{';
The cast is safe as long as we are in the ASCII set. Strings have to be converted with Encoding.UTF8.GetBytes()
.
State
The serializer mustn’t have state and will be used by several threads at the same time.
Further reading: JSON
Issue Analytics
- State:
- Created 5 years ago
- Comments:5 (5 by maintainers)
Top GitHub Comments
One more thing for the implementation… we do not want to serialize numbers with ToString. We have to decompose the numbers into their digits by using basic math and not by serializing it to string or anything. Every digit can then be written as byte to the output stream.
This one is coming with corefx 3.0
https://github.com/dotnet/corefx/blob/master/src/Common/src/CoreLib/System/Number.NumberBuffer.cs