Skip to content

Commit f388408

Browse files
committed
System.Text.Json serializer support
1 parent 82d97ca commit f388408

11 files changed

Lines changed: 338 additions & 10 deletions

File tree

Tocsoft.GraphQLCodeGen.Cli/Templates/cs/CSharp.template

Lines changed: 81 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,13 @@ using System.Collections.Generic;
66
using System.Linq;
77
using System.Net.Http;
88
using System.Threading.Tasks;
9+
10+
{{ifTemplate 'JsonConverter' 'Newtonsoft.Json'}}
911
using Newtonsoft.Json;
12+
{{/ifTemplate}}
13+
{{ifTemplate 'JsonConverter' 'System.Text.Json'}}
14+
using System.Text.Json;
15+
{{/ifTemplate}}
1016

1117
#line hidden
1218

@@ -22,13 +28,22 @@ namespace {{Namespace}}
2228
}
2329
{{~/if~}}
2430

31+
{{!# JsonConverter}} Newtonsoft.Json
32+
2533
{{!# HttpClientName}} HttpClient
2634
{{!# ClientExceptionBaseClass}} Exception
2735
{{!# interfaceBase }}
2836
{{!# jsonConverters }}
2937
{{!# BeforeInterfaceOperation}}
3038
{{!# StringifyEnums}} true
3139

40+
{{!# JsonPropertyAttributeName}}
41+
{{~ifTemplate 'JsonConverter' 'Newtonsoft.Json'~}}JsonProperty{{~/ifTemplate~}}
42+
{{~ifTemplate 'JsonConverter' 'System.Text.Json'}}System.Text.Json.Serialization.JsonPropertyName{{~/ifTemplate~}}
43+
{{!# JsonConverterAttributeName}}
44+
{{~ifTemplate 'JsonConverter' 'Newtonsoft.Json'~}}JsonConverter{{~/ifTemplate~}}
45+
{{~ifTemplate 'JsonConverter' 'System.Text.Json'}}System.Text.Json.Serialization.JsonConverter{{~/ifTemplate~}}
46+
3247
{{!# ClientException}}
3348
public sealed class {{ClassName}}Exception : {{> ClientExceptionBaseClass}}
3449
{
@@ -75,30 +90,57 @@ namespace {{Namespace}}
7590
{{!# ClientClass}}
7691
public sealed class {{ClassName}} : I{{ClassName}}
7792
{
93+
{{ifTemplate 'JsonConverter' 'Newtonsoft.Json'~}}
7894
private static JsonSerializerSettings jsonSettings = new JsonSerializerSettings()
7995
{
8096
Converters =
8197
{
8298
new Newtonsoft.Json.Converters.StringEnumConverter(camelCaseText: false),
8399
{{ > jsonConverters }}
84100
}
101+
};{{~/ifTemplate}}
102+
{{ifTemplate 'JsonConverter' 'System.Text.Json'~}}
103+
private static JsonSerializerOptions jsonOptions = new JsonSerializerOptions()
104+
{
105+
Converters =
106+
{
107+
new System.Text.Json.Serialization.JsonStringEnumConverter(),
108+
{{ > jsonConverters }}
109+
}
85110
};
111+
{{~/ifTemplate}}
86112

87113
private string SerializeBody(GraphQLRequest request)
88114
{
89-
return JsonConvert.SerializeObject(request, jsonSettings);
115+
{{ifTemplate 'JsonConverter' 'Newtonsoft.Json'~}}
116+
return JsonConvert.SerializeObject(request, jsonSettings);
117+
{{~/ifTemplate}}
118+
{{ifTemplate 'JsonConverter' 'System.Text.Json'~}}
119+
return JsonSerializer.Serialize(request, jsonOptions);
120+
{{~/ifTemplate}}
90121
}
91122

92123
private async Task<GraphQLResponseData<T>> DeserializeBodyAsync<T>(HttpResponseMessage response)
93124
{
94125
var jsonResult = await response.Content.ReadAsStringAsync();
126+
{{ifTemplate 'JsonConverter' 'Newtonsoft.Json'~}}
95127
var errorsResult = JsonConvert.DeserializeObject<GraphQLResponseErrors<T>>(jsonResult, jsonSettings);
128+
{{~/ifTemplate}}
129+
{{ifTemplate 'JsonConverter' 'System.Text.Json'~}}
130+
var errorsResult = JsonSerializer.Deserialize<GraphQLResponseErrors<T>>(jsonResult, jsonOptions);
131+
{{~/ifTemplate}}
96132

97133
if (errorsResult.errors?.Any() == true) {
98134
throw new {{ClassName}}Exception(errorsResult.errors.Select(x => x.Message), response);
99135
}
100136

101-
var result = JsonConvert.DeserializeObject<GraphQLResponseData<T>>(jsonResult, jsonSettings);
137+
{{ifTemplate 'JsonConverter' 'Newtonsoft.Json'~}}
138+
var result = JsonConvert.DeserializeObject<GraphQLResponseData<T>>(jsonResult, jsonSettings);
139+
{{~/ifTemplate}}
140+
{{ifTemplate 'JsonConverter' 'System.Text.Json'~}}
141+
var result = JsonSerializer.Deserialize<GraphQLResponseData<T>>(jsonResult, jsonOptions);
142+
{{~/ifTemplate}}
143+
102144
return result;
103145
}
104146

@@ -153,7 +195,7 @@ namespace {{Namespace}}
153195
public sealed class {{pascalCase Name}} {{#each~ Interfaces}}{{~#if @first}}: {{else}}, {{/if~}}I{{pascalCase .}}{{~/each}}
154196
{
155197
{{#each Fields}}
156-
[JsonProperty("{{Name}}")]
198+
[{{> JsonPropertyAttributeName}}("{{Name}}")]
157199
public {{> TypeReference Type}} {{pascalCase Name}} { get; set; }
158200
{{/each}}
159201
}
@@ -170,7 +212,7 @@ namespace {{Namespace}}
170212
{{!# Enum}}
171213

172214
{{ifTemplate 'StringifyEnums' 'true'}}
173-
[JsonConverter(typeof({{pascalCase Name}}.CustomJsonStringifiedEnumConverter))]
215+
[{{> JsonConverterAttributeName}}(typeof({{pascalCase Name}}.CustomJsonStringifiedEnumConverter))]
174216
public class {{pascalCase Name}}
175217
{
176218
static Dictionary<string, {{pascalCase Name}}> lookup = new Dictionary<string, {{pascalCase Name}}>();
@@ -212,7 +254,8 @@ namespace {{Namespace}}
212254
public static readonly {{pascalCase ../Name}} {{pascalCase .}} = Create("{{.}}", true);
213255
{{/each}}
214256

215-
internal class CustomJsonStringifiedEnumConverter : JsonConverter
257+
{{ifTemplate 'JsonConverter' 'Newtonsoft.Json'~}}
258+
internal class CustomJsonStringifiedEnumConverter : JsonConverter
216259
{
217260
public override bool CanConvert(Type objectType)
218261
{
@@ -238,9 +281,41 @@ namespace {{Namespace}}
238281
}
239282
}
240283
}
284+
{{~/ifTemplate}}
285+
{{ifTemplate 'JsonConverter' 'System.Text.Json'~}}
286+
internal class CustomJsonStringifiedEnumConverter : System.Text.Json.Serialization.JsonConverter<{{pascalCase Name}}>
287+
{
288+
public override bool CanConvert(Type typeToConvert)
289+
{
290+
return typeof({{pascalCase Name}}).IsAssignableFrom(typeToConvert);
291+
}
292+
293+
public override Episode Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
294+
{
295+
return reader.GetString();
296+
}
297+
298+
public override void Write(Utf8JsonWriter writer, {{pascalCase Name}} value, JsonSerializerOptions options)
299+
{
300+
if(value is null) {
301+
writer.WriteNullValue();
302+
}
303+
else
304+
{
305+
writer.WriteStringValue(value.Value);
306+
}
307+
}
308+
}
309+
{{~/ifTemplate}}
241310
}
242311
{{else}}
243-
[JsonConverter(typeof(Newtonsoft.Json.Converters.StringEnumConverter))]
312+
313+
{{ifTemplate 'JsonConverter' 'Newtonsoft.Json'~}}
314+
[{{> JsonConverterAttributeName}}(typeof(Newtonsoft.Json.Converters.StringEnumConverter))]
315+
{{/ifTemplate~}}
316+
{{ifTemplate 'JsonConverter' 'System.Text.Json'~}}
317+
[{{> JsonConverterAttributeName}}(typeof(System.Text.Json.Serialization.JsonStringEnumConverter))]
318+
{{/ifTemplate~}}
244319
public enum {{pascalCase Name}}
245320
{
246321
{{#each Values}}

Tocsoft.GraphQLCodeGen.Tests/CodeGeneratorTester.cs

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,10 @@ public void ConfigureResponse(Func<GraphQlQuery, object> httpIntercepter)
4141
}
4242

4343
private bool generated = false;
44-
44+
45+
private string clientClass = null;
46+
private string defaultOperationName;
47+
4548
public async Task<string> Generate()
4649
{
4750
if (generated) return this.GeneratedCode;
@@ -54,6 +57,7 @@ public async Task<string> Generate()
5457

5558
CodeGenerator generator = new CodeGenerator(logger, settings);
5659

60+
5761
await generator.LoadSource();
5862

5963
generator.Parse();
@@ -65,6 +69,11 @@ public async Task<string> Generate()
6569
this.GeneratedCode = generator.GeneratedCode;
6670

6771
this.Errors = generator.Document.Errors;
72+
this.clientClass = $"{generator.Model.Namespace}.{generator.Model.ClassName}";
73+
74+
this.defaultOperationName = generator.Model.Operations.First().Name.ToPascalCase() + "Async";
75+
76+
this.clientClass = $"{generator.Model.Namespace}.{generator.Model.ClassName}";
6877

6978
return this.GeneratedCode;
7079
}
@@ -80,6 +89,13 @@ public async Task Verify()
8089
this.httpClient.VerifyAll();
8190
}
8291
}
92+
93+
public Task<GraphQlQuery> ExecuteClient()
94+
=> ExecuteClient(this.clientClass, $"{this.defaultOperationName}()");
95+
96+
public Task<GraphQlQuery> ExecuteClient(string code)
97+
=> ExecuteClient(this.clientClass, code);
98+
8399
public async Task<GraphQlQuery> ExecuteClient(string clientName, string code)
84100
{
85101
var generatedCode = await Generate();

Tocsoft.GraphQLCodeGen.Tests/Compiler.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,11 @@ private static CSharpCompilation GenerateCode(string[] sourceCode)
6565
MetadataReference.CreateFromFile(typeof(Uri).Assembly.Location),
6666
MetadataReference.CreateFromFile(typeof(System.Net.Http.HttpClient).Assembly.Location),
6767
MetadataReference.CreateFromFile(typeof(Newtonsoft.Json.JsonWriter).Assembly.Location),
68+
69+
MetadataReference.CreateFromFile(typeof(ReadOnlySpan<byte>).Assembly.Location),
70+
MetadataReference.CreateFromFile(typeof(ReadOnlySpan<>).Assembly.Location),
71+
MetadataReference.CreateFromFile(typeof(System.Text.Json.JsonSerializer).Assembly.Location),
72+
6873
MetadataReference.CreateFromFile(typeof(Console).Assembly.Location),
6974
MetadataReference.CreateFromFile(typeof(System.Runtime.AssemblyTargetedPatchBandAttribute).Assembly.Location),
7075
MetadataReference.CreateFromFile(typeof(Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo).Assembly.Location),
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
mutation q($emp:Episode){
2+
test(id: $emp)
3+
{
4+
nullable,
5+
nonnullable
6+
}
7+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
query q{
2+
test(id: "safsa")
3+
{
4+
nullable,
5+
nonnullable
6+
}
7+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"root": true,
3+
"schema": "schema.gql",
4+
"output": "test.cs",
5+
"class": "Sample.Client.Test",
6+
7+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
2+
schema {
3+
query: Query
4+
mutation: Mutation
5+
}
6+
7+
type Query {
8+
test(id: string!): Droid
9+
}
10+
11+
type Mutation {
12+
test(id: Episode!): Droid
13+
}
14+
15+
type Droid {
16+
nullable: Episode
17+
nonnullable: Episode!
18+
}
19+
20+
enum Episode {
21+
NEWHOPE
22+
EMPIRE
23+
JEDI
24+
}

Tocsoft.GraphQLCodeGen.Tests/StringifiedEnums.cs renamed to Tocsoft.GraphQLCodeGen.Tests/StringifiedEnumsNewtonsoftJson.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,11 @@
55

66
namespace Tocsoft.GraphQLCodeGen.Tests
77
{
8-
public class StringifiedEnums
8+
public class StringifiedEnumsNewtonsoftJson
99
{
1010
private readonly CodeGeneratorTester tester;
1111

12-
public StringifiedEnums()
12+
public StringifiedEnumsNewtonsoftJson()
1313
{
1414
tester = new CodeGeneratorTester();
1515
}
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
using System;
2+
using System.Linq;
3+
using System.Threading.Tasks;
4+
using Xunit;
5+
6+
namespace Tocsoft.GraphQLCodeGen.Tests
7+
{
8+
public class StringifiedEnumsSystemTextJson
9+
{
10+
private readonly CodeGeneratorTester tester;
11+
12+
public StringifiedEnumsSystemTextJson()
13+
{
14+
tester = new CodeGeneratorTester();
15+
}
16+
17+
[Fact]
18+
public async Task SerilizesMutationValuesCorrectly_Stringified()
19+
{
20+
tester.AddQuery("./Files/StringifiedEnums/Mutation.gql");
21+
tester.Configure(s =>
22+
{
23+
s.TemplateSettings["StringifyEnums"] = "true";
24+
s.TemplateSettings["JsonConverter"] = "System.Text.Json";
25+
});
26+
27+
var query = await tester.ExecuteClient("Sample.Client.Test", @"MutationQAsync(Episode.Newhope)");
28+
29+
Assert.Equal("NEWHOPE", query.Variables["emp"]);
30+
}
31+
32+
[Fact]
33+
public async Task SerilizesMutationValuesCorrectly_Enum()
34+
{
35+
tester.AddQuery("./Files/StringifiedEnums/Mutation.gql");
36+
tester.Configure(s =>
37+
{
38+
s.TemplateSettings["StringifyEnums"] = "false";
39+
s.TemplateSettings["JsonConverter"] = "System.Text.Json";
40+
});
41+
42+
var query = await tester.ExecuteClient("Sample.Client.Test", @"MutationQAsync(Episode.NEWHOPE)");
43+
44+
Assert.Equal("NEWHOPE", query.Variables["emp"]);
45+
}
46+
47+
[Fact]
48+
public async Task SettingExplicitlyTrue()
49+
{
50+
tester.AddQuery("./Files/StringifiedEnums/Query.gql");
51+
tester.Configure(s =>
52+
{
53+
s.TemplateSettings["StringifyEnums"] = "true";
54+
s.TemplateSettings["JsonConverter"] = "System.Text.Json";
55+
});
56+
57+
var code = await tester.Generate();
58+
59+
Assert.Contains(@"[System.Text.Json.Serialization.JsonConverter(typeof(Episode.CustomJsonStringifiedEnumConverter))]", code);
60+
Assert.Contains(@"public class Episode", code);
61+
62+
await tester.Verify();
63+
}
64+
65+
[Fact]
66+
public async Task SettingImplicitlyTrue()
67+
{
68+
tester.AddQuery("./Files/StringifiedEnums/Query.gql");
69+
tester.Configure(s =>
70+
{
71+
s.TemplateSettings["JsonConverter"] = "System.Text.Json";
72+
});
73+
var code = await tester.Generate();
74+
75+
Assert.Contains(@"[System.Text.Json.Serialization.JsonConverter(typeof(Episode.CustomJsonStringifiedEnumConverter))]", code);
76+
Assert.Contains(@"public class Episode", code);
77+
78+
await tester.Verify();
79+
}
80+
81+
[Fact]
82+
public async Task SettingExplicitlyFalse()
83+
{
84+
tester.AddQuery("./Files/StringifiedEnums/Query.gql");
85+
tester.Configure(s =>
86+
{
87+
s.TemplateSettings["StringifyEnums"] = "false";
88+
s.TemplateSettings["JsonConverter"] = "System.Text.Json";
89+
});
90+
91+
var code = await tester.Generate();
92+
93+
Assert.Contains(@"[System.Text.Json.Serialization.JsonConverter(typeof(System.Text.Json.Serialization.JsonStringEnumConverter))]", code);
94+
Assert.DoesNotContain(@"IStringifiedEnum", code);
95+
96+
Assert.Contains(@"public enum Episode", code);
97+
98+
await tester.Verify();
99+
}
100+
}
101+
}

0 commit comments

Comments
 (0)