Skip to content

Commit 5b4fbfe

Browse files
committed
Initial Implementation
1 parent f7d3802 commit 5b4fbfe

16 files changed

Lines changed: 423 additions & 0 deletions

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -348,3 +348,6 @@ MigrationBackup/
348348

349349
# Ionide (cross platform F# VS Code tools) working folder
350350
.ionide/
351+
.idea
352+
.DS_Store
353+
*.received.json

System.Text.Json.Protobuf.sln

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
2+
Microsoft Visual Studio Solution File, Format Version 12.00
3+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "System.Text.Json.Protobuf", "src\System.Text.Json.Protobuf\System.Text.Json.Protobuf.csproj", "{AE98589A-143B-41D2-9C20-1C071BA203AC}"
4+
EndProject
5+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "System.Text.Json.Protobuf.Tests", "test\System.Text.Json.Protobuf.Tests\System.Text.Json.Protobuf.Tests.csproj", "{97FF3953-1E5F-4D06-95A3-1956448C8E6F}"
6+
EndProject
7+
Global
8+
GlobalSection(SolutionConfigurationPlatforms) = preSolution
9+
Debug|Any CPU = Debug|Any CPU
10+
Release|Any CPU = Release|Any CPU
11+
EndGlobalSection
12+
GlobalSection(ProjectConfigurationPlatforms) = postSolution
13+
{AE98589A-143B-41D2-9C20-1C071BA203AC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
14+
{AE98589A-143B-41D2-9C20-1C071BA203AC}.Debug|Any CPU.Build.0 = Debug|Any CPU
15+
{AE98589A-143B-41D2-9C20-1C071BA203AC}.Release|Any CPU.ActiveCfg = Release|Any CPU
16+
{AE98589A-143B-41D2-9C20-1C071BA203AC}.Release|Any CPU.Build.0 = Release|Any CPU
17+
{97FF3953-1E5F-4D06-95A3-1956448C8E6F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
18+
{97FF3953-1E5F-4D06-95A3-1956448C8E6F}.Debug|Any CPU.Build.0 = Debug|Any CPU
19+
{97FF3953-1E5F-4D06-95A3-1956448C8E6F}.Release|Any CPU.ActiveCfg = Release|Any CPU
20+
{97FF3953-1E5F-4D06-95A3-1956448C8E6F}.Release|Any CPU.Build.0 = Release|Any CPU
21+
EndGlobalSection
22+
EndGlobal
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
using System.Text.Json.Serialization;
2+
using Google.Protobuf;
3+
using Google.Protobuf.Reflection;
4+
5+
namespace System.Text.Json.Protobuf;
6+
7+
internal class FieldConverter<T> : InternalConverter
8+
{
9+
private JsonConverter<T>? _converter;
10+
11+
public override void Write(Utf8JsonWriter writer, object value, JsonSerializerOptions options)
12+
{
13+
_converter ??= (JsonConverter<T>) options.GetConverter(typeof(T));
14+
_converter.Write(writer, (T) value, options);
15+
}
16+
17+
public override void Read(ref Utf8JsonReader reader, IMessage obj, Type typeToConvert, JsonSerializerOptions options,
18+
IFieldAccessor fieldAccessor)
19+
{
20+
var jsonConverter = (JsonConverter<T>) options.GetConverter(typeToConvert);
21+
var read = jsonConverter.Read(ref reader, typeToConvert, options);
22+
if (read is { } value)
23+
{
24+
fieldAccessor.SetValue(obj, value);
25+
}
26+
}
27+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
using Google.Protobuf.Reflection;
2+
3+
namespace System.Text.Json.Protobuf;
4+
5+
internal class FieldInfo
6+
{
7+
public IFieldAccessor Accessor { get; init; }
8+
public InternalConverter? Converter { get; set; }
9+
public bool IsRepeated { get; init; }
10+
public Type FieldType { get; init; }
11+
public string JsonName { get; set; }
12+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
using Google.Protobuf;
2+
using Google.Protobuf.Reflection;
3+
4+
namespace System.Text.Json.Protobuf;
5+
6+
internal abstract class InternalConverter
7+
{
8+
public abstract void Write(Utf8JsonWriter writer, object value, JsonSerializerOptions options);
9+
10+
public abstract void Read(ref Utf8JsonReader reader, IMessage obj, Type typeToConvert, JsonSerializerOptions options, IFieldAccessor fieldAccessor);
11+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
namespace System.Text.Json.Protobuf;
2+
3+
internal class InternalConverterFactory
4+
{
5+
public static InternalConverter Create(Type fieldType, bool isRepeated)
6+
{
7+
if (isRepeated)
8+
{
9+
var converterType = typeof(RepeatedFieldConverter<>).MakeGenericType(fieldType);
10+
var internalConverter = (InternalConverter) Activator.CreateInstance(converterType)!;
11+
return internalConverter;
12+
}
13+
else
14+
{
15+
var converterType = typeof(FieldConverter<>).MakeGenericType(fieldType);
16+
var internalConverter = (InternalConverter) Activator.CreateInstance(converterType)!;
17+
return internalConverter;
18+
}
19+
}
20+
}
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
using System.Reflection;
2+
using System.Text.Json.Serialization;
3+
using Google.Protobuf;
4+
using Google.Protobuf.Reflection;
5+
6+
namespace System.Text.Json.Protobuf;
7+
8+
internal class ProtobufConverter<T> : JsonConverter<T?> where T : class, IMessage, new()
9+
{
10+
private readonly List<FieldInfo> _fields = new();
11+
private readonly Dictionary<string, FieldInfo> _fieldsLookup = new();
12+
13+
public ProtobufConverter()
14+
{
15+
var propertyInfo = typeof(T).GetProperty("Descriptor", BindingFlags.Public | BindingFlags.Static);
16+
var messageDescriptor = (MessageDescriptor) propertyInfo?.GetValue(null, null)!;
17+
18+
foreach (var fieldDescriptor in messageDescriptor.Fields.InDeclarationOrder())
19+
{
20+
var fieldInfo = new FieldInfo
21+
{
22+
Accessor = fieldDescriptor.Accessor,
23+
IsRepeated = fieldDescriptor.IsRepeated,
24+
FieldType = GetFieldType(fieldDescriptor),
25+
JsonName = fieldDescriptor.JsonName,
26+
};
27+
_fieldsLookup.Add(fieldDescriptor.JsonName, fieldInfo);
28+
_fields.Add(fieldInfo);
29+
}
30+
}
31+
32+
public override T? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
33+
{
34+
var obj = new T();
35+
36+
if (reader.TokenType != JsonTokenType.StartObject)
37+
{
38+
throw new JsonException($"The JSON value could not be converted to {typeToConvert}.");
39+
}
40+
41+
// Process all properties.
42+
while (true)
43+
{
44+
// Read the property name or EndObject.
45+
reader.Read();
46+
47+
if (reader.TokenType == JsonTokenType.EndObject)
48+
{
49+
break;
50+
}
51+
52+
var propertyName = reader.GetString();
53+
54+
if (!_fieldsLookup.TryGetValue(propertyName, out var fieldInfo))
55+
{
56+
continue;
57+
}
58+
59+
fieldInfo.Converter ??= InternalConverterFactory.Create(fieldInfo.FieldType, fieldInfo.IsRepeated);
60+
61+
reader.Read();
62+
fieldInfo.Converter.Read(ref reader, obj, fieldInfo.FieldType, options, fieldInfo.Accessor);
63+
}
64+
65+
return obj;
66+
67+
}
68+
69+
public override void Write(Utf8JsonWriter writer, T? value, JsonSerializerOptions options)
70+
{
71+
if (value == null)
72+
{
73+
writer.WriteNullValue();
74+
return;
75+
}
76+
77+
writer.WriteStartObject();
78+
79+
foreach (var fieldInfo in _fields)
80+
{
81+
var obj = fieldInfo.Accessor.GetValue(value);
82+
if (obj is { } propertyValue)
83+
{
84+
writer.WritePropertyName(fieldInfo.JsonName);
85+
fieldInfo.Converter ??= InternalConverterFactory.Create(fieldInfo.FieldType, fieldInfo.IsRepeated);
86+
fieldInfo.Converter.Write(writer, propertyValue, options);
87+
}
88+
}
89+
90+
writer.WriteEndObject();
91+
}
92+
93+
private Type GetFieldType(FieldDescriptor fieldDescriptor)
94+
{
95+
switch (fieldDescriptor.FieldType)
96+
{
97+
case FieldType.Int32:
98+
return typeof(int);
99+
case FieldType.Int64:
100+
return typeof(long);
101+
default:
102+
throw new ArgumentOutOfRangeException();
103+
}
104+
}
105+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
using System.Text.Json.Serialization;
2+
using Google.Protobuf;
3+
4+
namespace System.Text.Json.Protobuf;
5+
6+
public class ProtobufJsonConverterFactory : JsonConverterFactory
7+
{
8+
public override bool CanConvert(Type typeToConvert)
9+
{
10+
return typeToConvert.IsAssignableTo(typeof(IMessage));
11+
}
12+
13+
public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options)
14+
{
15+
var converterType = typeof(ProtobufConverter<>).MakeGenericType(typeToConvert);
16+
return (JsonConverter) Activator.CreateInstance(converterType)!;
17+
}
18+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
using System.Text.Json.Serialization;
2+
using Google.Protobuf;
3+
using Google.Protobuf.Collections;
4+
using Google.Protobuf.Reflection;
5+
6+
namespace System.Text.Json.Protobuf;
7+
8+
internal class RepeatedFieldConverter<T> : InternalConverter
9+
{
10+
private JsonConverter<IList<T>>? _converter;
11+
12+
public override void Write(Utf8JsonWriter writer, object value, JsonSerializerOptions options)
13+
{
14+
_converter ??= (JsonConverter<IList<T>>) options.GetConverter(typeof(IList<T>));
15+
_converter.Write(writer, (IList<T>) value, options);
16+
}
17+
18+
public override void Read(ref Utf8JsonReader reader, IMessage obj, Type typeToConvert, JsonSerializerOptions options,
19+
IFieldAccessor fieldAccessor)
20+
{
21+
_converter ??= (JsonConverter<IList<T>>) options.GetConverter(typeof(IList<T>));
22+
23+
var elements = _converter.Read(ref reader, typeof(IList<T>), options);
24+
if (elements == null)
25+
{
26+
return;
27+
}
28+
29+
var value = (RepeatedField<T>)fieldAccessor.GetValue(obj);
30+
value.AddRange(elements);
31+
}
32+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>net6.0</TargetFramework>
5+
<ImplicitUsings>enable</ImplicitUsings>
6+
<Nullable>enable</Nullable>
7+
</PropertyGroup>
8+
9+
<ItemGroup>
10+
<PackageReference Include="Google.Protobuf" Version="3.19.4" />
11+
<PackageReference Include="System.Text.Json" Version="6.0.2" />
12+
</ItemGroup>
13+
14+
</Project>

0 commit comments

Comments
 (0)