From dad7edd648ebc38e045a789b693d9e69ffd12c64 Mon Sep 17 00:00:00 2001 From: yallie Date: Sat, 15 Aug 2020 02:07:58 +0300 Subject: [PATCH] Added System.Text.Json serializer project (doesn't work yet). Known issues: 1. Lacks support for DataContract/DataMember attributes: https://github.com/dotnet/runtime/issues/29975 https://github.com/dotnet/runtime/issues/30009 2. Deserializes the primitive values as JsonElement instances. --- .../Internal/GenericMessage.cs | 23 ++++ .../Internal/IRequestMessage.cs | 7 + .../Internal/IResponseMessage.cs | 7 + .../Internal/RequestMsg.cs | 12 ++ .../Internal/ResponseMsg.cs | 11 ++ ...rvices.Serialization.SystemTextJson.csproj | 43 +++++++ .../Serializer.cs | 121 ++++++++++++++++++ .../JsonServices.Serialization.Tests.csproj | 3 +- .../SerializerTestsSystemTextJson.cs | 12 ++ src/JsonServices.sln | 6 + 10 files changed, 244 insertions(+), 1 deletion(-) create mode 100644 src/JsonServices.Serialization.SystemTextJson/Internal/GenericMessage.cs create mode 100644 src/JsonServices.Serialization.SystemTextJson/Internal/IRequestMessage.cs create mode 100644 src/JsonServices.Serialization.SystemTextJson/Internal/IResponseMessage.cs create mode 100644 src/JsonServices.Serialization.SystemTextJson/Internal/RequestMsg.cs create mode 100644 src/JsonServices.Serialization.SystemTextJson/Internal/ResponseMsg.cs create mode 100644 src/JsonServices.Serialization.SystemTextJson/JsonServices.Serialization.SystemTextJson.csproj create mode 100644 src/JsonServices.Serialization.SystemTextJson/Serializer.cs create mode 100644 src/JsonServices.Serialization.Tests/SerializerTestsSystemTextJson.cs diff --git a/src/JsonServices.Serialization.SystemTextJson/Internal/GenericMessage.cs b/src/JsonServices.Serialization.SystemTextJson/Internal/GenericMessage.cs new file mode 100644 index 0000000..4f6329a --- /dev/null +++ b/src/JsonServices.Serialization.SystemTextJson/Internal/GenericMessage.cs @@ -0,0 +1,23 @@ +using System.Text.Json.Serialization; +using JsonServices.Messages; + +namespace JsonServices.Serialization.SystemTextJson.Internal +{ + internal class GenericMessage + { + [JsonPropertyName("jsonrpc")] + public string Version { get; set; } + + [JsonPropertyName("method")] + public string Name { get; set; } + + [JsonPropertyName("error")] + public Error Error { get; set; } + + [JsonPropertyName("id")] + public string Id { get; set; } + + public bool IsValid => Version == "2.0" && + (!string.IsNullOrWhiteSpace(Name) || !string.IsNullOrWhiteSpace(Id)); + } +} diff --git a/src/JsonServices.Serialization.SystemTextJson/Internal/IRequestMessage.cs b/src/JsonServices.Serialization.SystemTextJson/Internal/IRequestMessage.cs new file mode 100644 index 0000000..6cd7013 --- /dev/null +++ b/src/JsonServices.Serialization.SystemTextJson/Internal/IRequestMessage.cs @@ -0,0 +1,7 @@ +namespace JsonServices.Serialization.SystemTextJson.Internal +{ + internal interface IRequestMessage + { + object Parameters { get; } + } +} diff --git a/src/JsonServices.Serialization.SystemTextJson/Internal/IResponseMessage.cs b/src/JsonServices.Serialization.SystemTextJson/Internal/IResponseMessage.cs new file mode 100644 index 0000000..708360b --- /dev/null +++ b/src/JsonServices.Serialization.SystemTextJson/Internal/IResponseMessage.cs @@ -0,0 +1,7 @@ +namespace JsonServices.Serialization.SystemTextJson.Internal +{ + internal interface IResponseMessage + { + object Result { get; } + } +} diff --git a/src/JsonServices.Serialization.SystemTextJson/Internal/RequestMsg.cs b/src/JsonServices.Serialization.SystemTextJson/Internal/RequestMsg.cs new file mode 100644 index 0000000..f682408 --- /dev/null +++ b/src/JsonServices.Serialization.SystemTextJson/Internal/RequestMsg.cs @@ -0,0 +1,12 @@ +using System.Runtime.Serialization; +using System.Text.Json.Serialization; + +namespace JsonServices.Serialization.SystemTextJson.Internal +{ + internal class RequestMsg : IRequestMessage + { + [JsonPropertyName("params")] + public T Parameters { get; set; } + object IRequestMessage.Parameters => Parameters; + } +} diff --git a/src/JsonServices.Serialization.SystemTextJson/Internal/ResponseMsg.cs b/src/JsonServices.Serialization.SystemTextJson/Internal/ResponseMsg.cs new file mode 100644 index 0000000..e23d77d --- /dev/null +++ b/src/JsonServices.Serialization.SystemTextJson/Internal/ResponseMsg.cs @@ -0,0 +1,11 @@ +using System.Text.Json.Serialization; + +namespace JsonServices.Serialization.SystemTextJson.Internal +{ + internal class ResponseMsg : IResponseMessage + { + [JsonPropertyName("result")] + public T Result { get; set; } + object IResponseMessage.Result => Result; + } +} diff --git a/src/JsonServices.Serialization.SystemTextJson/JsonServices.Serialization.SystemTextJson.csproj b/src/JsonServices.Serialization.SystemTextJson/JsonServices.Serialization.SystemTextJson.csproj new file mode 100644 index 0000000..51c4815 --- /dev/null +++ b/src/JsonServices.Serialization.SystemTextJson/JsonServices.Serialization.SystemTextJson.csproj @@ -0,0 +1,43 @@ + + + + JsonServices.Serialization.SystemTextJson + 0.0.0.1 + yallie + Copyright Alexey Yakovlev 2020. All Rights Reserved. + C# Message-Based JSON-RPC Client over WebSockets + https://github.com/yallie/JsonService/blob/master/LICENSE + https://github.com/yallie/JsonService + https://github.com/yallie/JsonService + websockets json rpc events + net461;netstandard2.0 + + + + ..\JsonServices.ruleset + JsonServices.Serialization.SystemTextJson + JsonServices.Serialization.SystemTextJson + + + + Full + True + ..\..\bin + + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers + + + + \ No newline at end of file diff --git a/src/JsonServices.Serialization.SystemTextJson/Serializer.cs b/src/JsonServices.Serialization.SystemTextJson/Serializer.cs new file mode 100644 index 0000000..ba57b5d --- /dev/null +++ b/src/JsonServices.Serialization.SystemTextJson/Serializer.cs @@ -0,0 +1,121 @@ +using System; +using System.Text.Json; +using JsonServices.Exceptions; +using JsonServices.Messages; +using JsonServices.Serialization.SystemTextJson.Internal; +using JsonServices.Services; + +namespace JsonServices.Serialization.SystemTextJson +{ + public class Serializer : ISerializer + { + public string Serialize(IMessage message) => JsonSerializer.Serialize(message); + + public IMessage Deserialize(string data, IMessageTypeProvider typeProvider, IMessageNameProvider nameProvider) + { + var preview = default(GenericMessage); + try + { + preview = JsonSerializer.Deserialize(data); + } + catch + { + // invalid message format + } + + if (preview == null || !preview.IsValid) + { + throw new InvalidRequestException(data) + { + MessageId = preview?.Id, + }; + } + + // detect message name + var name = preview.Name; + var isRequest = name != null; + if (name == null) + { + // server cannot handle a response message + if (nameProvider == null) + { + throw new InvalidRequestException(data) + { + MessageId = preview.Id, + }; + } + + // invalid request id + name = nameProvider.TryGetMessageName(preview.Id); + if (name == null) + { + throw new InvalidRequestException(name) + { + MessageId = preview.Id, + }; + } + } + + try + { + // deserialize request or response message + if (isRequest) + { + return DeserializeRequest(data, name, preview.Id, typeProvider); + } + + return DeserializeResponse(data, name, preview.Id, preview.Error, typeProvider); + } + catch (JsonServicesException ex) + { + // make sure MessageId is reported + if (ex.MessageId == null) + { + ex.MessageId = preview.Id; + } + + throw; + } + catch (Exception ex) + { + throw new InvalidRequestException(data, ex) + { + MessageId = preview.Id, + }; + } + } + + private RequestMessage DeserializeRequest(string data, string name, string id, IMessageTypeProvider typeProvider) + { + // get the message request type + var type = typeProvider.GetRequestType(name); + var msgType = typeof(RequestMsg<>).MakeGenericType(new[] { type }); + + // deserialize the strong-typed message + var reqMsg = (IRequestMessage)JsonSerializer.Deserialize(data, msgType); + return new RequestMessage + { + Name = name, + Parameters = reqMsg.Parameters, + Id = id, + }; + } + + public ResponseMessage DeserializeResponse(string data, string name, string id, Error error, IMessageTypeProvider typeProvider) + { + // pre-deserialize to get the bulk of the message + var type = typeProvider.GetResponseType(name); + + // handle void messages + if (type == typeof(void)) + { + return ResponseMessage.Create(null, error, id); + } + + // deserialize the strong-typed message + var msgType = typeof(ResponseMsg<>).MakeGenericType(new[] { type }); + var respMsg = (IResponseMessage)JsonSerializer.Deserialize(data, msgType); + return ResponseMessage.Create(respMsg.Result, error, id); + } + } +} diff --git a/src/JsonServices.Serialization.Tests/JsonServices.Serialization.Tests.csproj b/src/JsonServices.Serialization.Tests/JsonServices.Serialization.Tests.csproj index 2bec9e5..907931d 100644 --- a/src/JsonServices.Serialization.Tests/JsonServices.Serialization.Tests.csproj +++ b/src/JsonServices.Serialization.Tests/JsonServices.Serialization.Tests.csproj @@ -10,7 +10,7 @@ https://github.com/yallie/JsonService https://github.com/yallie/JsonService websockets json rpc events - net46;netcoreapp2.0 + net461;netcoreapp2.0 JsonServices.Serialization.Tests JsonServices.Serialization.Tests @@ -32,6 +32,7 @@ + \ No newline at end of file diff --git a/src/JsonServices.Serialization.Tests/SerializerTestsSystemTextJson.cs b/src/JsonServices.Serialization.Tests/SerializerTestsSystemTextJson.cs new file mode 100644 index 0000000..41ecf19 --- /dev/null +++ b/src/JsonServices.Serialization.Tests/SerializerTestsSystemTextJson.cs @@ -0,0 +1,12 @@ +using JsonServices.Serialization; +using JsonServices.Serialization.SystemTextJson; +using NUnit.Framework; + +namespace JsonServices.Tests.Serialization +{ + [TestFixture, Ignore("Doesn't work yet")] + public class SerializerTestsSystemTextJson : SerializerTestsBase + { + protected override ISerializer Serializer { get; } = new Serializer(); + } +} diff --git a/src/JsonServices.sln b/src/JsonServices.sln index 7be9804..a25a122 100644 --- a/src/JsonServices.sln +++ b/src/JsonServices.sln @@ -35,6 +35,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JsonServices.Auth.SecureRem EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JsonServices.Sample.CoreServer", "..\sample\sample-core-server\JsonServices.Sample.CoreServer.csproj", "{CA33030D-A387-49CD-9329-042F7B464067}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JsonServices.Serialization.SystemTextJson", "JsonServices.Serialization.SystemTextJson\JsonServices.Serialization.SystemTextJson.csproj", "{EE4F39FE-8EFB-4A98-9EFE-953007F4CB74}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -105,6 +107,10 @@ Global {CA33030D-A387-49CD-9329-042F7B464067}.Debug|Any CPU.Build.0 = Debug|Any CPU {CA33030D-A387-49CD-9329-042F7B464067}.Release|Any CPU.ActiveCfg = Release|Any CPU {CA33030D-A387-49CD-9329-042F7B464067}.Release|Any CPU.Build.0 = Release|Any CPU + {EE4F39FE-8EFB-4A98-9EFE-953007F4CB74}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EE4F39FE-8EFB-4A98-9EFE-953007F4CB74}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EE4F39FE-8EFB-4A98-9EFE-953007F4CB74}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EE4F39FE-8EFB-4A98-9EFE-953007F4CB74}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE