Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -8,28 +8,34 @@ namespace GraphQL.Client.Abstractions.Websocket {
/// <summary>
/// A Subscription Request
/// </summary>
public class GraphQLWebSocketRequest : IEquatable<GraphQLWebSocketRequest> {
public class GraphQLWebSocketRequest : Dictionary<string, object>, IEquatable<GraphQLWebSocketRequest> {
public const string IdKey = "id";
public const string TypeKey = "type";
public const string PayloadKey = "payload";

/// <summary>
/// The Identifier of the Response
/// </summary>
[DataMember(Name = IdKey)]
public virtual string Id { get; set; }
public string Id {
get => ContainsKey(IdKey) ? (string)this[IdKey] : null;
set => this[IdKey] = value;
}

/// <summary>
/// The Type of the Request
/// </summary>
[DataMember(Name = TypeKey)]
public virtual string Type { get; set; }
public string Type {
get => ContainsKey(TypeKey) ? (string)this[TypeKey] : null;
set => this[TypeKey] = value;
}

/// <summary>
/// The payload of the websocket request
/// </summary>
[DataMember(Name = PayloadKey)]
public virtual GraphQLRequest Payload { get; set; }
public GraphQLRequest Payload {
get => ContainsKey(PayloadKey) ? (GraphQLRequest) this[PayloadKey] : null;
set => this[PayloadKey] = value;
}

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

Expand Down
21 changes: 0 additions & 21 deletions src/GraphQL.Client.Serializer.Newtonsoft/GraphQLRequest.cs

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,11 @@ private void ConfigureMandatorySerializerOptions() {
}

public string SerializeToString(GraphQL.GraphQLRequest request) {
return JsonConvert.SerializeObject(new GraphQLRequest(request), JsonSerializerSettings);
return JsonConvert.SerializeObject(request, JsonSerializerSettings);
}

public byte[] SerializeToBytes(Abstractions.Websocket.GraphQLWebSocketRequest request) {
var json = JsonConvert.SerializeObject(new GraphQLWebSocketRequest(request), JsonSerializerSettings);
var json = JsonConvert.SerializeObject(request, JsonSerializerSettings);
return Encoding.UTF8.GetBytes(json);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,6 @@
<PackageReference Include="System.Text.Json" Version="4.7.0" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="Dahomey.Json" Version="1.6.0" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\GraphQL.Client.Abstractions.Websocket\GraphQL.Client.Abstractions.Websocket.csproj" />
</ItemGroup>
Expand Down
21 changes: 0 additions & 21 deletions src/GraphQL.Client.Serializer.SystemTextJson/GraphQLRequest.cs

This file was deleted.

This file was deleted.

166 changes: 166 additions & 0 deletions src/GraphQL.Client.Serializer.SystemTextJson/ImmutableConverter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Text.Json;
using System.Text.Json.Serialization;

namespace GraphQL.Client.Serializer.SystemTextJson {

/// <summary>
/// class for converting immutable objects, derived from https://github.com/manne/obviously/blob/master/src/system.text.json/Core/ImmutableConverter.cs
/// </summary>
public class ImmutableConverter : JsonConverter<object> {
public override bool CanConvert(Type typeToConvert) {
bool result;
var constructors = typeToConvert.GetConstructors(BindingFlags.Public | BindingFlags.Instance);
if (constructors.Length != 1) {
result = false;
}
else {
var constructor = constructors[0];
var parameters = constructor.GetParameters();
var hasParameters = parameters.Length > 0;
if (hasParameters) {
var properties = typeToConvert.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
result = true;
foreach (var parameter in parameters) {
var hasMatchingProperty = properties.Any(p =>
NameOfPropertyAndParameter.Matches(p.Name, parameter.Name, typeToConvert.IsAnonymous()));
if (!hasMatchingProperty) {
result = false;
break;
}
}
}
else {
result = false;
}
}

return result;
}

public override object Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) {
var valueOfProperty = new Dictionary<PropertyInfo, object>();
var namedPropertiesMapping = GetNamedProperties(options, GetProperties(typeToConvert));
reader.Read();
while (true) {
if (reader.TokenType != JsonTokenType.PropertyName && reader.TokenType != JsonTokenType.String) {
break;
}

var jsonPropName = reader.GetString();
var normalizedPropName = ConvertAndNormalizeName(jsonPropName, options);
if (!namedPropertiesMapping.TryGetValue(normalizedPropName, out var obProp)) {
reader.Read();
}
else {
var value = JsonSerializer.Deserialize(ref reader, obProp.PropertyType, options);
reader.Read();
valueOfProperty[obProp] = value;
}
}

var ctor = typeToConvert.GetConstructors(BindingFlags.Public | BindingFlags.Instance).First();
var parameters = ctor.GetParameters();
var parameterValues = new object[parameters.Length];
for (var index = 0; index < parameters.Length; index++) {
var parameterInfo = parameters[index];
var value = valueOfProperty.First(prop =>
NameOfPropertyAndParameter.Matches(prop.Key.Name, parameterInfo.Name, typeToConvert.IsAnonymous())).Value;

parameterValues[index] = value;
}

var instance = ctor.Invoke(parameterValues);
return instance;
}

public override void Write(Utf8JsonWriter writer, object value, JsonSerializerOptions options) {
var strippedOptions = new JsonSerializerOptions {
AllowTrailingCommas = options.AllowTrailingCommas,
DefaultBufferSize = options.DefaultBufferSize,
DictionaryKeyPolicy = options.DictionaryKeyPolicy,
Encoder = options.Encoder,
IgnoreNullValues = options.IgnoreNullValues,
IgnoreReadOnlyProperties = options.IgnoreReadOnlyProperties,
MaxDepth = options.MaxDepth,
PropertyNameCaseInsensitive = options.PropertyNameCaseInsensitive,
PropertyNamingPolicy = options.PropertyNamingPolicy,
ReadCommentHandling = options.ReadCommentHandling,
WriteIndented = options.WriteIndented
};
foreach (var converter in options.Converters) {
if (!(converter is ImmutableConverter))
strippedOptions.Converters.Add(converter);
}
JsonSerializer.Serialize(writer, value, strippedOptions);
}

private static PropertyInfo[] GetProperties(IReflect typeToConvert) {
return typeToConvert.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
}

private static IReadOnlyDictionary<string, PropertyInfo> GetNamedProperties(JsonSerializerOptions options, IEnumerable<PropertyInfo> properties) {
var result = new Dictionary<string, PropertyInfo>();
foreach (var property in properties) {
string name;
var nameAttribute = property.GetCustomAttribute<JsonPropertyNameAttribute>();
if (nameAttribute != null) {
name = nameAttribute.Name;
}
else {
name = options.PropertyNamingPolicy?.ConvertName(property.Name) ?? property.Name;
}

var normalizedName = NormalizeName(name, options);
result.Add(normalizedName, property);
}

return result;
}

private static string ConvertAndNormalizeName(string name, JsonSerializerOptions options) {
var convertedName = options.PropertyNamingPolicy?.ConvertName(name) ?? name;
return options.PropertyNameCaseInsensitive ? convertedName.ToLowerInvariant() : convertedName;
}

private static string NormalizeName(string name, JsonSerializerOptions options) {
return options.PropertyNameCaseInsensitive ? name.ToLowerInvariant() : name;
}
}

internal static class NameOfPropertyAndParameter {
public static bool Matches(string propertyName, string parameterName, bool anonymousType) {
if (propertyName is null && parameterName is null) {
return true;
}

if (propertyName is null || parameterName is null) {
return false;
}

if (anonymousType) {
return propertyName.Equals(parameterName, StringComparison.Ordinal);
}
else {
var xRight = propertyName.AsSpan(1);
var yRight = parameterName.AsSpan(1);
return char.ToLowerInvariant(propertyName[0]).CompareTo(parameterName[0]) == 0 && xRight.Equals(yRight, StringComparison.Ordinal);
}
}
}

internal static class TypeExtensions {
// copied from https://github.com/dahomey-technologies/Dahomey.Json/blob/master/src/Dahomey.Json/Util/TypeExtensions.cs
public static bool IsAnonymous(this Type type) {
return type.Namespace == null
&& type.IsSealed
&& type.BaseType == typeof(object)
&& !type.IsPublic
&& type.IsDefined(typeof(CompilerGeneratedAttribute), false);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using System.Text.Json;

namespace GraphQL.Client.Serializer.SystemTextJson {
public static class JsonSerializerOptionsExtensions {
public static JsonSerializerOptions SetupImmutableConverter(
this JsonSerializerOptions options) {
options.Converters.Add(new ImmutableConverter());
return options;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using Dahomey.Json;
using GraphQL.Client.Abstractions;
using GraphQL.Client.Abstractions.Websocket;

Expand All @@ -13,8 +12,8 @@ public class SystemTextJsonSerializer: IGraphQLWebsocketJsonSerializer
{
public static JsonSerializerOptions DefaultJsonSerializerOptions => new JsonSerializerOptions {
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
}.SetupExtensions();

}.SetupImmutableConverter();
public JsonSerializerOptions Options { get; }

public SystemTextJsonSerializer() : this(DefaultJsonSerializerOptions) { }
Expand All @@ -34,15 +33,15 @@ private void ConfigureMandatorySerializerOptions() {
}

public string SerializeToString(GraphQL.GraphQLRequest request) {
return JsonSerializer.Serialize(new GraphQLRequest(request), Options);
return JsonSerializer.Serialize(request, Options);
}

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

public byte[] SerializeToBytes(Abstractions.Websocket.GraphQLWebSocketRequest request) {
return JsonSerializer.SerializeToUtf8Bytes(new GraphQLWebSocketRequest(request), Options);
return JsonSerializer.SerializeToUtf8Bytes(request, Options);
}

public Task<WebsocketMessageWrapper> DeserializeToWebsocketResponseWrapperAsync(Stream stream) {
Expand Down
Loading