Skip to content

Commit 52c8731

Browse files
committed
use a Dictionary as a base for GraphQLRequest
1 parent 9fb7fbd commit 52c8731

File tree

15 files changed

+227
-115
lines changed

15 files changed

+227
-115
lines changed

src/GraphQL.Client.Abstractions.Websocket/GraphQLWebSocketRequest.cs

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,28 +8,34 @@ namespace GraphQL.Client.Abstractions.Websocket {
88
/// <summary>
99
/// A Subscription Request
1010
/// </summary>
11-
public class GraphQLWebSocketRequest : IEquatable<GraphQLWebSocketRequest> {
11+
public class GraphQLWebSocketRequest : Dictionary<string, object>, IEquatable<GraphQLWebSocketRequest> {
1212
public const string IdKey = "id";
1313
public const string TypeKey = "type";
1414
public const string PayloadKey = "payload";
1515

1616
/// <summary>
1717
/// The Identifier of the Response
1818
/// </summary>
19-
[DataMember(Name = IdKey)]
20-
public virtual string Id { get; set; }
19+
public string Id {
20+
get => ContainsKey(IdKey) ? (string)this[IdKey] : null;
21+
set => this[IdKey] = value;
22+
}
2123

2224
/// <summary>
2325
/// The Type of the Request
2426
/// </summary>
25-
[DataMember(Name = TypeKey)]
26-
public virtual string Type { get; set; }
27+
public string Type {
28+
get => ContainsKey(TypeKey) ? (string)this[TypeKey] : null;
29+
set => this[TypeKey] = value;
30+
}
2731

2832
/// <summary>
2933
/// The payload of the websocket request
3034
/// </summary>
31-
[DataMember(Name = PayloadKey)]
32-
public virtual GraphQLRequest Payload { get; set; }
35+
public GraphQLRequest Payload {
36+
get => ContainsKey(PayloadKey) ? (GraphQLRequest) this[PayloadKey] : null;
37+
set => this[PayloadKey] = value;
38+
}
3339

3440
private TaskCompletionSource<bool> _tcs = new TaskCompletionSource<bool>();
3541

src/GraphQL.Client.Serializer.Newtonsoft/GraphQLRequest.cs

Lines changed: 0 additions & 21 deletions
This file was deleted.

src/GraphQL.Client.Serializer.Newtonsoft/GraphQLWebSocketRequest.cs

Lines changed: 0 additions & 23 deletions
This file was deleted.

src/GraphQL.Client.Serializer.Newtonsoft/NewtonsoftJsonSerializer.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,11 @@ private void ConfigureMandatorySerializerOptions() {
3434
}
3535

3636
public string SerializeToString(GraphQL.GraphQLRequest request) {
37-
return JsonConvert.SerializeObject(new GraphQLRequest(request), JsonSerializerSettings);
37+
return JsonConvert.SerializeObject(request, JsonSerializerSettings);
3838
}
3939

4040
public byte[] SerializeToBytes(Abstractions.Websocket.GraphQLWebSocketRequest request) {
41-
var json = JsonConvert.SerializeObject(new GraphQLWebSocketRequest(request), JsonSerializerSettings);
41+
var json = JsonConvert.SerializeObject(request, JsonSerializerSettings);
4242
return Encoding.UTF8.GetBytes(json);
4343
}
4444

src/GraphQL.Client.Serializer.SystemTextJson/GraphQLRequest.cs

Lines changed: 0 additions & 21 deletions
This file was deleted.

src/GraphQL.Client.Serializer.SystemTextJson/GraphQLWebSocketRequest.cs

Lines changed: 0 additions & 23 deletions
This file was deleted.
Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Reflection;
5+
using System.Runtime.CompilerServices;
6+
using System.Text.Json;
7+
using System.Text.Json.Serialization;
8+
9+
namespace GraphQL.Client.Serializer.SystemTextJson {
10+
11+
/// <summary>
12+
/// class for converting immutable objects, derived from https://github.com/manne/obviously/blob/master/src/system.text.json/Core/ImmutableConverter.cs
13+
/// </summary>
14+
public class ImmutableConverter : JsonConverter<object> {
15+
public override bool CanConvert(Type typeToConvert) {
16+
bool result;
17+
var constructors = typeToConvert.GetConstructors(BindingFlags.Public | BindingFlags.Instance);
18+
if (constructors.Length != 1) {
19+
result = false;
20+
}
21+
else {
22+
var constructor = constructors[0];
23+
var parameters = constructor.GetParameters();
24+
var hasParameters = parameters.Length > 0;
25+
if (hasParameters) {
26+
var properties = typeToConvert.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
27+
result = true;
28+
foreach (var parameter in parameters) {
29+
var hasMatchingProperty = properties.Any(p =>
30+
NameOfPropertyAndParameter.Matches(p.Name, parameter.Name, typeToConvert.IsAnonymous()));
31+
if (!hasMatchingProperty) {
32+
result = false;
33+
break;
34+
}
35+
}
36+
}
37+
else {
38+
result = false;
39+
}
40+
}
41+
42+
return result;
43+
}
44+
45+
public override object Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) {
46+
var valueOfProperty = new Dictionary<PropertyInfo, object>();
47+
var namedPropertiesMapping = GetNamedProperties(options, GetProperties(typeToConvert));
48+
reader.Read();
49+
while (true) {
50+
if (reader.TokenType != JsonTokenType.PropertyName && reader.TokenType != JsonTokenType.String) {
51+
break;
52+
}
53+
54+
var jsonPropName = reader.GetString();
55+
var normalizedPropName = ConvertAndNormalizeName(jsonPropName, options);
56+
if (!namedPropertiesMapping.TryGetValue(normalizedPropName, out var obProp)) {
57+
reader.Read();
58+
}
59+
else {
60+
var value = JsonSerializer.Deserialize(ref reader, obProp.PropertyType, options);
61+
reader.Read();
62+
valueOfProperty[obProp] = value;
63+
}
64+
}
65+
66+
var ctor = typeToConvert.GetConstructors(BindingFlags.Public | BindingFlags.Instance).First();
67+
var parameters = ctor.GetParameters();
68+
var parameterValues = new object[parameters.Length];
69+
for (var index = 0; index < parameters.Length; index++) {
70+
var parameterInfo = parameters[index];
71+
var value = valueOfProperty.First(prop =>
72+
NameOfPropertyAndParameter.Matches(prop.Key.Name, parameterInfo.Name, typeToConvert.IsAnonymous())).Value;
73+
74+
parameterValues[index] = value;
75+
}
76+
77+
var instance = ctor.Invoke(parameterValues);
78+
return instance;
79+
}
80+
81+
public override void Write(Utf8JsonWriter writer, object value, JsonSerializerOptions options) {
82+
var strippedOptions = new JsonSerializerOptions {
83+
AllowTrailingCommas = options.AllowTrailingCommas,
84+
DefaultBufferSize = options.DefaultBufferSize,
85+
DictionaryKeyPolicy = options.DictionaryKeyPolicy,
86+
Encoder = options.Encoder,
87+
IgnoreNullValues = options.IgnoreNullValues,
88+
IgnoreReadOnlyProperties = options.IgnoreReadOnlyProperties,
89+
MaxDepth = options.MaxDepth,
90+
PropertyNameCaseInsensitive = options.PropertyNameCaseInsensitive,
91+
PropertyNamingPolicy = options.PropertyNamingPolicy,
92+
ReadCommentHandling = options.ReadCommentHandling,
93+
WriteIndented = options.WriteIndented
94+
};
95+
foreach (var converter in options.Converters) {
96+
if (!(converter is ImmutableConverter))
97+
strippedOptions.Converters.Add(converter);
98+
}
99+
JsonSerializer.Serialize(writer, value, strippedOptions);
100+
}
101+
102+
private static PropertyInfo[] GetProperties(IReflect typeToConvert) {
103+
return typeToConvert.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
104+
}
105+
106+
private static IReadOnlyDictionary<string, PropertyInfo> GetNamedProperties(JsonSerializerOptions options, IEnumerable<PropertyInfo> properties) {
107+
var result = new Dictionary<string, PropertyInfo>();
108+
foreach (var property in properties) {
109+
string name;
110+
var nameAttribute = property.GetCustomAttribute<JsonPropertyNameAttribute>();
111+
if (nameAttribute != null) {
112+
name = nameAttribute.Name;
113+
}
114+
else {
115+
name = options.PropertyNamingPolicy?.ConvertName(property.Name) ?? property.Name;
116+
}
117+
118+
var normalizedName = NormalizeName(name, options);
119+
result.Add(normalizedName, property);
120+
}
121+
122+
return result;
123+
}
124+
125+
private static string ConvertAndNormalizeName(string name, JsonSerializerOptions options) {
126+
var convertedName = options.PropertyNamingPolicy?.ConvertName(name) ?? name;
127+
return options.PropertyNameCaseInsensitive ? convertedName.ToLowerInvariant() : convertedName;
128+
}
129+
130+
private static string NormalizeName(string name, JsonSerializerOptions options) {
131+
return options.PropertyNameCaseInsensitive ? name.ToLowerInvariant() : name;
132+
}
133+
}
134+
135+
internal static class NameOfPropertyAndParameter {
136+
public static bool Matches(string propertyName, string parameterName, bool anonymousType) {
137+
if (propertyName is null && parameterName is null) {
138+
return true;
139+
}
140+
141+
if (propertyName is null || parameterName is null) {
142+
return false;
143+
}
144+
145+
if (anonymousType) {
146+
return propertyName.Equals(parameterName, StringComparison.Ordinal);
147+
}
148+
else {
149+
var xRight = propertyName.AsSpan(1);
150+
var yRight = parameterName.AsSpan(1);
151+
return char.ToLowerInvariant(propertyName[0]).CompareTo(parameterName[0]) == 0 && xRight.Equals(yRight, StringComparison.Ordinal);
152+
}
153+
}
154+
}
155+
156+
internal static class TypeExtensions {
157+
// copied from https://github.com/dahomey-technologies/Dahomey.Json/blob/master/src/Dahomey.Json/Util/TypeExtensions.cs
158+
public static bool IsAnonymous(this Type type) {
159+
return type.Namespace == null
160+
&& type.IsSealed
161+
&& type.BaseType == typeof(object)
162+
&& !type.IsPublic
163+
&& type.IsDefined(typeof(CompilerGeneratedAttribute), false);
164+
}
165+
}
166+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Text;
4+
using System.Text.Json;
5+
using System.Text.Json.Serialization;
6+
using Dahomey.Json;
7+
using Dahomey.Json.Serialization.Converters.Factories;
8+
9+
namespace GraphQL.Client.Serializer.SystemTextJson {
10+
public static class JsonSerializerOptionsExtensions {
11+
public static JsonSerializerOptions SetupDahomeyExtensions(
12+
this JsonSerializerOptions options) {
13+
options.Converters.Add(new ImmutableConverter());
14+
//options.Converters.Add((JsonConverter)new JsonSerializerOptionsState(options));
15+
//options.Converters.Add((JsonConverter)new DictionaryConverterFactory());
16+
//options.Converters.Add((JsonConverter)new CollectionConverterFactory());
17+
//options.Converters.Add((JsonConverter)new JsonNodeConverterFactory());
18+
//options.Converters.Add((JsonConverter)new ObjectConverterFactory());
19+
return options;
20+
}
21+
}
22+
}

src/GraphQL.Client.Serializer.SystemTextJson/SystemTextJsonSerializer.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@ public class SystemTextJsonSerializer: IGraphQLWebsocketJsonSerializer
1313
{
1414
public static JsonSerializerOptions DefaultJsonSerializerOptions => new JsonSerializerOptions {
1515
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
16-
}.SetupExtensions();
17-
16+
}.SetupDahomeyExtensions();
17+
1818
public JsonSerializerOptions Options { get; }
1919

2020
public SystemTextJsonSerializer() : this(DefaultJsonSerializerOptions) { }
@@ -34,15 +34,15 @@ private void ConfigureMandatorySerializerOptions() {
3434
}
3535

3636
public string SerializeToString(GraphQL.GraphQLRequest request) {
37-
return JsonSerializer.Serialize(new GraphQLRequest(request), Options);
37+
return JsonSerializer.Serialize(request, Options);
3838
}
3939

4040
public Task<GraphQLResponse<TResponse>> DeserializeFromUtf8StreamAsync<TResponse>(Stream stream, CancellationToken cancellationToken) {
4141
return JsonSerializer.DeserializeAsync<GraphQLResponse<TResponse>>(stream, Options, cancellationToken).AsTask();
4242
}
4343

4444
public byte[] SerializeToBytes(Abstractions.Websocket.GraphQLWebSocketRequest request) {
45-
return JsonSerializer.SerializeToUtf8Bytes(new GraphQLWebSocketRequest(request), Options);
45+
return JsonSerializer.SerializeToUtf8Bytes(request, Options);
4646
}
4747

4848
public Task<WebsocketMessageWrapper> DeserializeToWebsocketResponseWrapperAsync(Stream stream) {

0 commit comments

Comments
 (0)