Skip to content

Commit bda92e6

Browse files
Add JSON support for LanguageExt NewType (#32)
* Add generic JSON converter for LanguageExt NewType * Improve .csproj files and build pipeline
1 parent bce0f82 commit bda92e6

9 files changed

Lines changed: 1166 additions & 43 deletions

File tree

Dbosoft.Functional.sln

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dbosoft.Functional", "src\D
77
EndProject
88
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dbosoft.Functional.Tests", "test\Dbosoft.Functional.Tests\Dbosoft.Functional.Tests.csproj", "{ADA73324-0DE5-479D-A63B-888EB922984E}"
99
EndProject
10+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dbosoft.Functional.Json", "src\Dbosoft.Functional.Json\Dbosoft.Functional.Json.csproj", "{1060C36D-CF26-4932-A9E6-F2C5BC32B8B7}"
11+
EndProject
12+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dbosoft.Functional.Json.Tests", "test\Dbosoft.Functional.Json.Tests\Dbosoft.Functional.Json.Tests.csproj", "{E6E91D63-DB2C-4B6F-A701-4F1BA558AB0F}"
13+
EndProject
1014
Global
1115
GlobalSection(SolutionConfigurationPlatforms) = preSolution
1216
Debug|Any CPU = Debug|Any CPU
@@ -21,6 +25,14 @@ Global
2125
{ADA73324-0DE5-479D-A63B-888EB922984E}.Debug|Any CPU.Build.0 = Debug|Any CPU
2226
{ADA73324-0DE5-479D-A63B-888EB922984E}.Release|Any CPU.ActiveCfg = Release|Any CPU
2327
{ADA73324-0DE5-479D-A63B-888EB922984E}.Release|Any CPU.Build.0 = Release|Any CPU
28+
{1060C36D-CF26-4932-A9E6-F2C5BC32B8B7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
29+
{1060C36D-CF26-4932-A9E6-F2C5BC32B8B7}.Debug|Any CPU.Build.0 = Debug|Any CPU
30+
{1060C36D-CF26-4932-A9E6-F2C5BC32B8B7}.Release|Any CPU.ActiveCfg = Release|Any CPU
31+
{1060C36D-CF26-4932-A9E6-F2C5BC32B8B7}.Release|Any CPU.Build.0 = Release|Any CPU
32+
{E6E91D63-DB2C-4B6F-A701-4F1BA558AB0F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
33+
{E6E91D63-DB2C-4B6F-A701-4F1BA558AB0F}.Debug|Any CPU.Build.0 = Debug|Any CPU
34+
{E6E91D63-DB2C-4B6F-A701-4F1BA558AB0F}.Release|Any CPU.ActiveCfg = Release|Any CPU
35+
{E6E91D63-DB2C-4B6F-A701-4F1BA558AB0F}.Release|Any CPU.Build.0 = Release|Any CPU
2436
EndGlobalSection
2537
GlobalSection(SolutionProperties) = preSolution
2638
HideSolutionNode = FALSE

azure-pipelines.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@ variables:
1414
buildConfiguration: 'Release'
1515

1616
steps:
17+
- task: UseGitVersion@5
18+
inputs:
19+
versionSpec: '5.11.x'
20+
1721
- task: DotNetCoreCLI@2
1822
inputs:
1923
command: restore
Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
using System;
2+
using System.Text.Json;
3+
using System.Text.Json.Serialization;
4+
using LanguageExt;
5+
using LanguageExt.TypeClasses;
6+
7+
namespace Dbosoft.Functional.Json.DataTypes;
8+
9+
/// <summary>
10+
/// <para>
11+
/// A JSON converter which supports <see cref="NewType{NEWTYPE,A, PRED, ORD}"/>.
12+
/// </para>
13+
/// <para>
14+
/// The <see cref="NewType{NEWTYPE,A, PRED, ORD}"/> must be based on one of the
15+
/// following types: <type><see cref="string"/></type>,
16+
/// <type><see cref="bool"/></type>, <type><see cref="short"/></type>
17+
/// <type><see cref="int"/></type>, <type><see cref="long"/></type>
18+
/// <type><see cref="byte"/></type>, <type><see cref="ushort"/></type>
19+
/// <type><see cref="uint"/></type>, <type><see cref="ulong"/></type>,
20+
/// <type><see cref="float"/></type>, <type><see cref="double"/></type>,
21+
/// <type><see cref="decimal"/></type>.
22+
/// </para>
23+
/// <para>
24+
/// <see cref="NewType{NEWTYPE,A, PRED, ORD}"/>s with <see cref="string"/> as
25+
/// the base value can be used as keys in dictionaries, objects, etc.
26+
/// </para>
27+
/// </summary>
28+
public class NewTypeJsonConverter : JsonConverterFactory
29+
{
30+
/// <inheritdoc />
31+
public override bool CanConvert(Type typeToConvert)
32+
{
33+
var newType = FindNewType(typeToConvert);
34+
if (newType is null || !newType.IsGenericType || newType.GetGenericTypeDefinition() != typeof(NewType<,,,>))
35+
return false;
36+
37+
var arguments = newType.GetGenericArguments();
38+
if (arguments.Length != 4)
39+
return false;
40+
41+
var a = arguments[1];
42+
return a == typeof(string) || a == typeof(bool)
43+
|| a == typeof(short) || a == typeof(int) || a == typeof(long)
44+
|| a == typeof(byte) || a == typeof(ushort) || a == typeof(uint) || a == typeof(ulong)
45+
|| a == typeof(float) || a == typeof(double) || a == typeof(decimal);
46+
}
47+
48+
/// <inheritdoc />
49+
public override JsonConverter CreateConverter(
50+
Type typeToConvert,
51+
JsonSerializerOptions options)
52+
{
53+
var newType = FindNewType(typeToConvert);
54+
if (newType is null || !newType.IsGenericType || newType.GetGenericTypeDefinition() != typeof(NewType<,,,>))
55+
throw new ArgumentException("The type is not a LanguageExt NewType.", nameof(typeToConvert));
56+
57+
var arguments = newType.GetGenericArguments();
58+
var a = arguments[1];
59+
var pred = arguments[2];
60+
var ord = arguments[3];
61+
62+
if (a == typeof(string))
63+
{
64+
return (JsonConverter)Activator.CreateInstance(
65+
typeof(NewTypeJsonConverter<,,,>)
66+
.MakeGenericType(typeToConvert, typeToConvert, pred, ord));
67+
}
68+
69+
if (a == typeof(bool) || a == typeof(short) || a == typeof(int) || a == typeof(long)
70+
|| a == typeof(byte) || a == typeof(ushort) || a == typeof(uint) || a == typeof(ulong)
71+
|| a == typeof(float) || a == typeof(double) || a == typeof(decimal))
72+
{
73+
return (JsonConverter)Activator.CreateInstance(
74+
typeof(NewTypeJsonConverter<,,,,>)
75+
.MakeGenericType(typeToConvert, typeToConvert, a, pred, ord));
76+
}
77+
78+
throw new ArgumentException($"The value type {a.Name} is not supported.", nameof(typeToConvert));
79+
}
80+
81+
private static Type? FindNewType(Type type)
82+
{
83+
var candidate = type;
84+
while (candidate is not null && candidate != typeof(object))
85+
{
86+
if (candidate.IsGenericType && candidate.GetGenericTypeDefinition() == typeof(NewType<,,,>))
87+
return candidate;
88+
candidate = candidate.BaseType;
89+
}
90+
91+
return null;
92+
}
93+
}
94+
95+
internal class NewTypeJsonConverter<T, NEWTYPE, PRED, ORD>
96+
: JsonConverter<T>
97+
where T : NewType<NEWTYPE, string, PRED, ORD>
98+
where PRED : struct, Pred<string>
99+
where NEWTYPE : NewType<NEWTYPE, string, PRED, ORD>
100+
where ORD : struct, Ord<string>
101+
{
102+
public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
103+
{
104+
return (T)Activator.CreateInstance(typeToConvert, reader.GetString());
105+
}
106+
107+
public override T ReadAsPropertyName(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
108+
{
109+
return (T)Activator.CreateInstance(typeToConvert, reader.GetString());
110+
}
111+
112+
public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options)
113+
{
114+
writer.WriteStringValue(value.Value);
115+
}
116+
117+
public override void WriteAsPropertyName(Utf8JsonWriter writer, T value, JsonSerializerOptions options)
118+
{
119+
writer.WritePropertyName(value.Value);
120+
}
121+
}
122+
123+
internal class NewTypeJsonConverter<T, NEWTYPE, A, PRED, ORD>
124+
: JsonConverter<T>
125+
where T : NewType<NEWTYPE, A, PRED, ORD>
126+
where PRED : struct, Pred<A>
127+
where NEWTYPE : NewType<NEWTYPE, A, PRED, ORD>
128+
where ORD : struct, Ord<A>
129+
{
130+
public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
131+
{
132+
object? value = typeof(A) switch
133+
{
134+
{ } t when t == typeof(bool) => reader.GetBoolean(),
135+
{ } t when t == typeof(byte) => reader.GetByte(),
136+
{ } t when t == typeof(short) => reader.GetInt16(),
137+
{ } t when t == typeof(int) => reader.GetInt32(),
138+
{ } t when t == typeof(long) => reader.GetInt64(),
139+
{ } t when t == typeof(ushort) => reader.GetUInt16(),
140+
{ } t when t == typeof(uint) => reader.GetUInt32(),
141+
{ } t when t == typeof(ulong) => reader.GetUInt64(),
142+
{ } t when t == typeof(float) => reader.GetSingle(),
143+
{ } t when t == typeof(double) => reader.GetDouble(),
144+
{ } t when t == typeof(decimal) => reader.GetDecimal(),
145+
_ => throw new InvalidOperationException($"Values of type {typeof(A).Name} are not supported.")
146+
};
147+
return (T)Activator.CreateInstance(typeToConvert, value);
148+
}
149+
150+
public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options)
151+
{
152+
switch (value.Value)
153+
{
154+
case bool v:
155+
writer.WriteBooleanValue(v);
156+
return;
157+
case byte v:
158+
writer.WriteNumberValue(v);
159+
return;
160+
case short v:
161+
writer.WriteNumberValue(v);
162+
return;
163+
case int v:
164+
writer.WriteNumberValue(v);
165+
return;
166+
case long v:
167+
writer.WriteNumberValue(v);
168+
return;
169+
case ushort v:
170+
writer.WriteNumberValue(v);
171+
return;
172+
case uint v:
173+
writer.WriteNumberValue(v);
174+
return;
175+
case ulong v:
176+
writer.WriteNumberValue(v);
177+
break;
178+
case float v:
179+
writer.WriteNumberValue(v);
180+
return;
181+
case double v:
182+
writer.WriteNumberValue(v);
183+
return;
184+
case decimal v:
185+
writer.WriteNumberValue(v);
186+
return;
187+
default:
188+
throw new ArgumentException("Values of this type are not supported.", nameof(value));
189+
}
190+
}
191+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>netstandard2.0</TargetFramework>
5+
<Nullable>enable</Nullable>
6+
<PackageId>Dbosoft.Functional.Json</PackageId>
7+
<Description>JSON support for LanguageExt NewType.</Description>
8+
</PropertyGroup>
9+
10+
<ItemGroup>
11+
<PackageReference Include="LanguageExt.Core" Version="[4.4.0,4.5.0)" />
12+
<PackageReference Include="System.Text.Json" Version="8.0.5" />
13+
</ItemGroup>
14+
15+
<ItemGroup>
16+
<ProjectReference Include="..\Dbosoft.Functional\Dbosoft.Functional.csproj" />
17+
</ItemGroup>
18+
19+
</Project>
Lines changed: 0 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,12 @@
11
<Project Sdk="Microsoft.NET.Sdk">
2-
32
<PropertyGroup>
43
<TargetFramework>netstandard2.0</TargetFramework>
5-
<LangVersion>10</LangVersion>
6-
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
7-
<PackageRequireLicenseAcceptance>false</PackageRequireLicenseAcceptance>
8-
<PackageLicenseExpression>MIT</PackageLicenseExpression>
9-
<PackageProjectUrl>https://github.com/dbosoft/Dbosoft.Functional</PackageProjectUrl>
10-
<Copyright>dbosoft GmbH</Copyright>
11-
<Authors>dbosoft</Authors>
12-
<Company>dbosoft GmbH</Company>
13-
<Product>Dbosoft.Functional</Product>
14-
<RepositoryUrl>https://github.com/dbosoft/Dbosoft.Functional</RepositoryUrl>
154
<PackageId>Dbosoft.Functional</PackageId>
165
<Description>Functional helper library based on language.ext.</Description>
17-
18-
<!-- Declare that the Repository URL can be published to NuSpec -->
19-
<PublishRepositoryUrl>true</PublishRepositoryUrl>
20-
<!-- Embed source files that are not tracked by the source control manager to the PDB -->
21-
<EmbedUntrackedSources>true</EmbedUntrackedSources>
22-
<!-- Include PDB in the built .nupkg -->
23-
<AllowedOutputExtensionsInPackageBuildOutputFolder>$(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb</AllowedOutputExtensionsInPackageBuildOutputFolder>
24-
25-
<GenerateDocumentationFile>true</GenerateDocumentationFile>
26-
</PropertyGroup>
27-
28-
<PropertyGroup>
29-
<ContinuousIntegrationBuild Condition="'$(TF_BUILD)' == 'true'">True</ContinuousIntegrationBuild>
30-
<ContinuousIntegrationBuild Condition="'$(GITHUB_ACTIONS)' == 'true'">True</ContinuousIntegrationBuild>
316
</PropertyGroup>
327

338
<ItemGroup>
34-
<PackageReference Include="GitVersion.MsBuild" Version="5.*">
35-
<PrivateAssets>all</PrivateAssets>
36-
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
37-
</PackageReference>
389
<PackageReference Include="LanguageExt.Core" Version="[4.4.0,4.5.0)" />
3910
<PackageReference Include="System.Threading.Tasks.Dataflow" Version="[5,)" />
4011
</ItemGroup>
41-
4212
</Project>

src/Directory.Build.props

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,40 @@
1-
21
<Project>
2+
33
<PropertyGroup>
4-
<!-- Declare that the Repository URL can be published to NuSpec -->
4+
<PackageLicenseExpression>MIT</PackageLicenseExpression>
5+
<PackageRequireLicenseAcceptance>false</PackageRequireLicenseAcceptance>
6+
<PackageProjectUrl>https://github.com/dbosoft/Dbosoft.Functional</PackageProjectUrl>
7+
<PackageReleaseNotes>https://github.com/dbosoft/Dbosoft.Functional/releases</PackageReleaseNotes>
8+
<Authors>dbosoft GmbH</Authors>
9+
<Company>dbosoft GmbH</Company>
10+
<Product>Dbosoft.Functional</Product>
11+
<Copyright>dbosoft GmbH. All rights reserved.</Copyright>
12+
<RepositoryUrl>https://github.com/dbosoft/Dbosoft.Functional</RepositoryUrl>
13+
<!-- Declare that the Repository URL can be published to NuSpec -->
514
<PublishRepositoryUrl>true</PublishRepositoryUrl>
615
<!-- Embed source files that are not tracked by the source control manager to the PDB -->
716
<EmbedUntrackedSources>true</EmbedUntrackedSources>
817
<!-- Include PDB in the built .nupkg -->
918
<AllowedOutputExtensionsInPackageBuildOutputFolder>$(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb</AllowedOutputExtensionsInPackageBuildOutputFolder>
10-
1119
<GenerateDocumentationFile>true</GenerateDocumentationFile>
12-
1320
</PropertyGroup>
1421

22+
<PropertyGroup>
23+
<LangVersion>12</LangVersion>
24+
<NoWarn>CS1591</NoWarn>
25+
</PropertyGroup>
1526

1627
<PropertyGroup>
1728
<ContinuousIntegrationBuild Condition="'$(TF_BUILD)' == 'true'">True</ContinuousIntegrationBuild>
1829
<ContinuousIntegrationBuild Condition="'$(GITHUB_ACTIONS)' == 'true'">True</ContinuousIntegrationBuild>
1930
</PropertyGroup>
2031

2132
<ItemGroup>
22-
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="8.*" PrivateAssets="All"/>
33+
<PackageReference Include="GitVersion.MsBuild" Version="5.11.1">
34+
<PrivateAssets>all</PrivateAssets>
35+
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
36+
</PackageReference>
37+
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="8.0.0" PrivateAssets="All"/>
2338
</ItemGroup>
39+
2440
</Project>

0 commit comments

Comments
 (0)