Last active
February 5, 2022 04:50
-
-
Save MelbourneDeveloper/efae92a34af0f2ade0c6f6ee10105b2c to your computer and use it in GitHub Desktop.
Typeless GraphQL Select
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using GraphQLParser; | |
using GraphQLParser.AST; | |
using Jayse; | |
using Microsoft.VisualStudio.TestTools.UnitTesting; | |
using Newtonsoft.Json; | |
using System.Collections; | |
using System.Collections.Immutable; | |
namespace TypelessGraphQL; | |
#pragma warning disable IDE1006 // Naming Styles | |
#pragma warning disable CA2201 // Do not raise reserved exception types | |
#pragma warning disable CA1062 // Validate arguments of public methods | |
#pragma warning disable IDE0007 // Use implicit type | |
/* | |
.NET 6 unit test project that uses a GraphQL query to select on an object's | |
JSON, return the JSON and deserialize back to the original object. | |
It doesn't use types.The idea is to explore what is possible without | |
defining types | |
<PackageReference Include="GraphQL-Parser" Version="7.2.0" /> | |
<PackageReference Include="Jayse" Version="0.6.0-alpha" /> | |
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.11.0" /> | |
<PackageReference Include="MSTest.TestAdapter" Version="2.2.7" /> | |
<PackageReference Include="MSTest.TestFramework" Version="2.2.7" /> | |
<PackageReference Include="coverlet.collector" Version="3.1.0" /> | |
*/ | |
public record Root(ImmutableList<Item> items); | |
public record Item(string name, int id, string? category, Details details); | |
public record Details(Location location, Size size, bool isround); | |
public record Location(double latitude, double longitude); | |
public record Size(double width, double height); | |
public class SelectionNode | |
{ | |
public string Name { get; } | |
public List<SelectionNode> Children { get; } = new List<SelectionNode>(); | |
public SelectionNode(string name) => Name = name; | |
public override string ToString() => Name; | |
} | |
public static class ProcessingExtensions | |
{ | |
public static string ToSelectionName(this GraphQLName selectionName) => selectionName.Value.Span.ToString(); | |
public static SelectionNode ToSelectionNodes(this string graphQL) | |
{ | |
var graphQlDocument = Parser.Parse(graphQL); | |
if (graphQlDocument.Definitions == null) throw new Exception("It's broken"); | |
var rootSelectionNode = new SelectionNode("Root"); | |
foreach (dynamic definition in graphQlDocument.Definitions) | |
{ | |
foreach (var selection in (IEnumerable)definition.SelectionSet.Selections) | |
{ | |
ProcessSelectionNode(selection, rootSelectionNode); | |
} | |
} | |
return rootSelectionNode; | |
} | |
public static JsonValue Process(this SelectionNode selectionNode, JsonValue inputJsonValue) => | |
inputJsonValue.ValueType switch | |
{ | |
JsonValueType.OfString => inputJsonValue, | |
JsonValueType.OfNumber => inputJsonValue, | |
JsonValueType.OfObject => | |
new JsonValue( | |
new OrderedImmutableDictionary<string, JsonValue>( | |
selectionNode.Children.ToDictionary(childSelectionNode => | |
childSelectionNode.Name, childSelectionNode => | |
//Note this converts undefined to null. Perhaps this is a hole in Jayse? | |
inputJsonValue.ObjectValue.ContainsKey(childSelectionNode.Name) | |
? Process(childSelectionNode, inputJsonValue.ObjectValue[childSelectionNode.Name]) : | |
new JsonValue())) | |
), | |
JsonValueType.OfArray => | |
new JsonValue | |
(inputJsonValue.ArrayValue.Select(jsonValue => | |
Process(selectionNode, jsonValue)).ToImmutableList() | |
), | |
JsonValueType.OfBoolean => inputJsonValue, | |
JsonValueType.OfNull => inputJsonValue, | |
_ => throw new NotImplementedException(), | |
}; | |
private static void ProcessSelectionNode(dynamic definition, SelectionNode parent) | |
{ | |
var selectionNodeName = ((GraphQLName)definition.Name).ToSelectionName(); | |
var selectionNode = new SelectionNode(selectionNodeName); | |
parent.Children.Add(selectionNode); | |
if (definition.SelectionSet == null) return; | |
foreach (dynamic selection in definition.SelectionSet.Selections) | |
{ | |
ProcessSelectionNode(selection, selectionNode); | |
} | |
} | |
} | |
[TestClass] | |
public class Tests | |
{ | |
[TestMethod] | |
public void TestTransformObjectWithTypelessGraphQL() | |
{ | |
//Our input model | |
var expectedData = new Root(ImmutableList.Create(new Item[] { | |
new Item("TheName1", 1, null, new Details(new Location(100, 200),new Size(50,100), true)), | |
new Item("TheName2", 2, null, new Details(new Location(102, 202),new Size(52, 102), true)) | |
})); | |
//Convert input to JSON | |
var expectedJson = JsonConvert.SerializeObject(expectedData); | |
//Convert the input JSON in to a Jayse JSON model | |
var inputJsonValue = expectedJson.ToJsonObject(); | |
//Parse our GraphQL query in to a selection model we can work with | |
var rootSelectionNode = | |
"{ items { id name category details { location { latitude longitude } size { width height} isround undefinedValue } } }" | |
.ToSelectionNodes(); | |
//Process the GraphQL selections | |
var outputJsonModel = new Dictionary<string, JsonValue>(); | |
foreach (var selectionNode in rootSelectionNode.Children) | |
{ | |
outputJsonModel.Add(selectionNode.Name, selectionNode.Process(inputJsonValue[selectionNode.Name])); | |
} | |
//Convert the output Jayse model in to JSON | |
var actualJson = outputJsonModel.ToJson(); | |
//Derialize the JSON back to the original model | |
var actualSelectedData = JsonConvert.DeserializeObject<Root>(actualJson); | |
//Ensure that the model stayed intact | |
if (actualSelectedData == null) throw new Exception(); | |
Assert.AreEqual(expectedData.items.Count, actualSelectedData.items.Count); | |
for (var i = 0; i < expectedData.items.Count; i++) | |
{ | |
Assert.IsTrue(expectedData.items[i].Equals(actualSelectedData.items[i])); | |
} | |
//Check that the output model has the added undefined value from the GraphQL selection | |
Assert.IsTrue(outputJsonModel["items"][0]["details"]["undefinedValue"].ValueType == JsonValueType.OfNull); | |
} | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment