Skip to content

Commit 306877f

Browse files
committed
Add JsonSerializerContext support
1 parent 7ae4ae4 commit 306877f

File tree

5 files changed

+350
-24
lines changed

5 files changed

+350
-24
lines changed

ValveKeyValue/ValveKeyValue.Test/Helpers/KVSerializerExtensions.cs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
using System.Text.Json.Serialization;
2+
13
namespace ValveKeyValue.Test
24
{
35
static class KVSerializerExtensions
@@ -37,5 +39,17 @@ public static TObject Deserialize<TObject>(this KVSerializer serializer, string
3739

3840
return serializer.Deserialize<TObject>(ms, options);
3941
}
42+
43+
public static TObject Deserialize<TObject>(this KVSerializer serializer, string text, JsonSerializerContext jsonContext, KVSerializerOptions options = null)
44+
{
45+
using var ms = new MemoryStream();
46+
using var writer = new StreamWriter(ms);
47+
writer.Write(text);
48+
writer.Flush();
49+
50+
ms.Seek(0, SeekOrigin.Begin);
51+
52+
return serializer.Deserialize<TObject>(ms, jsonContext, options);
53+
}
4054
}
4155
}

ValveKeyValue/ValveKeyValue.Test/Test Data/apisurface.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,7 @@ public sealed enum ValveKeyValue.KVSerializationFormat
282282
public class ValveKeyValue.KVSerializer
283283
{
284284
public static ValveKeyValue.KVSerializer Create(ValveKeyValue.KVSerializationFormat format);
285+
public ValveKeyValue.TObject Deserialize<ValveKeyValue.TObject>(System.IO.Stream stream, System.Text.Json.Serialization.JsonSerializerContext jsonContext, ValveKeyValue.KVSerializerOptions options);
285286
public ValveKeyValue.KVDocument Deserialize(System.IO.Stream stream, ValveKeyValue.KVSerializerOptions options);
286287
public ValveKeyValue.TObject Deserialize<ValveKeyValue.TObject>(System.IO.Stream stream, ValveKeyValue.KVSerializerOptions options);
287288
public bool Equals(object obj);
@@ -291,6 +292,7 @@ public class ValveKeyValue.KVSerializer
291292
protected object MemberwiseClone();
292293
public void Serialize(System.IO.Stream stream, ValveKeyValue.KVDocument data, ValveKeyValue.KVSerializerOptions options);
293294
public void Serialize(System.IO.Stream stream, ValveKeyValue.KVObject data, ValveKeyValue.KVSerializerOptions options);
295+
public void Serialize<ValveKeyValue.TData>(System.IO.Stream stream, ValveKeyValue.TData data, string name, System.Text.Json.Serialization.JsonSerializerContext jsonContext, ValveKeyValue.KVSerializerOptions options);
294296
public void Serialize<ValveKeyValue.TData>(System.IO.Stream stream, ValveKeyValue.TData data, string name, ValveKeyValue.KVSerializerOptions options);
295297
public string ToString();
296298
}
Lines changed: 235 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,235 @@
1+
using System.Text.Json.Serialization;
2+
3+
namespace ValveKeyValue.Test
4+
{
5+
partial class SourceGeneratorTestCase
6+
{
7+
static readonly KVSerializer Serializer = KVSerializer.Create(KVSerializationFormat.KeyValues1Text);
8+
9+
#region Test types
10+
11+
class PersonData
12+
{
13+
public string FirstName { get; set; }
14+
public string LastName { get; set; }
15+
public bool CanFixIt { get; set; }
16+
}
17+
18+
class PersonWithAttributes
19+
{
20+
[KVProperty("first_name")]
21+
public string FirstName { get; set; }
22+
23+
[KVProperty("last_name")]
24+
public string LastName { get; set; }
25+
26+
[KVIgnore]
27+
public string Secret { get; set; }
28+
}
29+
30+
class NestedData
31+
{
32+
public string Name { get; set; }
33+
public InnerData Inner { get; set; }
34+
}
35+
36+
class InnerData
37+
{
38+
public int Value { get; set; }
39+
}
40+
41+
// A type NOT registered in the source-gen context, to test fallback
42+
class UnregisteredType
43+
{
44+
public string Foo { get; set; }
45+
}
46+
47+
#endregion
48+
49+
#region Source-gen context
50+
51+
[JsonSerializable(typeof(PersonData))]
52+
[JsonSerializable(typeof(PersonWithAttributes))]
53+
[JsonSerializable(typeof(NestedData))]
54+
[JsonSerializable(typeof(InnerData))]
55+
[JsonSerializable(typeof(Dictionary<string, string>))]
56+
[JsonSerializable(typeof(Dictionary<string, int>))]
57+
[JsonSerializable(typeof(List<string>))]
58+
[JsonSerializable(typeof(string[]))]
59+
partial class TestJsonContext : JsonSerializerContext;
60+
61+
#endregion
62+
63+
#region Deserialization
64+
65+
[Test]
66+
public void BasicDeserialization()
67+
{
68+
var result = Serializer.Deserialize<PersonData>(
69+
"\"obj\" { \"FirstName\" \"Bob\" \"LastName\" \"Builder\" \"CanFixIt\" \"1\" }",
70+
TestJsonContext.Default);
71+
72+
Assert.That(result.FirstName, Is.EqualTo("Bob"));
73+
Assert.That(result.LastName, Is.EqualTo("Builder"));
74+
Assert.That(result.CanFixIt, Is.True);
75+
}
76+
77+
[Test]
78+
public void NestedObjectDeserialization()
79+
{
80+
var result = Serializer.Deserialize<NestedData>(
81+
"\"obj\" { \"Name\" \"parent\" \"Inner\" { \"Value\" \"42\" } }",
82+
TestJsonContext.Default);
83+
84+
Assert.That(result.Name, Is.EqualTo("parent"));
85+
Assert.That(result.Inner, Is.Not.Null);
86+
Assert.That(result.Inner.Value, Is.EqualTo(42));
87+
}
88+
89+
[Test]
90+
public void DictionaryDeserialization()
91+
{
92+
var result = Serializer.Deserialize<Dictionary<string, string>>(
93+
"\"obj\" { \"key1\" \"value1\" \"key2\" \"value2\" }",
94+
TestJsonContext.Default);
95+
96+
Assert.That(result["key1"], Is.EqualTo("value1"));
97+
Assert.That(result["key2"], Is.EqualTo("value2"));
98+
}
99+
100+
[Test]
101+
public void DictionaryWithIntValues()
102+
{
103+
var result = Serializer.Deserialize<Dictionary<string, int>>(
104+
"\"obj\" { \"a\" \"1\" \"b\" \"2\" }",
105+
TestJsonContext.Default);
106+
107+
Assert.That(result["a"], Is.EqualTo(1));
108+
Assert.That(result["b"], Is.EqualTo(2));
109+
}
110+
111+
[Test]
112+
public void ListDeserialization()
113+
{
114+
var result = Serializer.Deserialize<List<string>>(
115+
"\"obj\" { \"0\" \"alpha\" \"1\" \"beta\" \"2\" \"gamma\" }",
116+
TestJsonContext.Default);
117+
118+
Assert.That(result, Has.Count.EqualTo(3));
119+
Assert.That(result[0], Is.EqualTo("alpha"));
120+
Assert.That(result[1], Is.EqualTo("beta"));
121+
Assert.That(result[2], Is.EqualTo("gamma"));
122+
}
123+
124+
[Test]
125+
public void ArrayDeserialization()
126+
{
127+
var result = Serializer.Deserialize<string[]>(
128+
"\"obj\" { \"0\" \"x\" \"1\" \"y\" }",
129+
TestJsonContext.Default);
130+
131+
Assert.That(result, Has.Length.EqualTo(2));
132+
Assert.That(result[0], Is.EqualTo("x"));
133+
Assert.That(result[1], Is.EqualTo("y"));
134+
}
135+
136+
#endregion
137+
138+
#region KVProperty and KVIgnore with source-gen
139+
140+
[Test]
141+
public void KVPropertyAttributeHonoredWithSourceGen()
142+
{
143+
var result = Serializer.Deserialize<PersonWithAttributes>(
144+
"\"obj\" { \"first_name\" \"Jane\" \"last_name\" \"Doe\" }",
145+
TestJsonContext.Default);
146+
147+
Assert.That(result.FirstName, Is.EqualTo("Jane"));
148+
Assert.That(result.LastName, Is.EqualTo("Doe"));
149+
}
150+
151+
[Test]
152+
public void KVIgnoreAttributeHonoredWithSourceGen()
153+
{
154+
var result = Serializer.Deserialize<PersonWithAttributes>(
155+
"\"obj\" { \"first_name\" \"Jane\" \"last_name\" \"Doe\" \"Secret\" \"hidden\" }",
156+
TestJsonContext.Default);
157+
158+
Assert.That(result.FirstName, Is.EqualTo("Jane"));
159+
Assert.That(result.Secret, Is.Null);
160+
}
161+
162+
#endregion
163+
164+
#region Serialization
165+
166+
[Test]
167+
public void BasicSerialization()
168+
{
169+
var data = new PersonData
170+
{
171+
FirstName = "Bob",
172+
LastName = "Builder",
173+
CanFixIt = true,
174+
};
175+
176+
string text;
177+
using (var ms = new MemoryStream())
178+
{
179+
Serializer.Serialize(ms, data, "root", TestJsonContext.Default);
180+
ms.Seek(0, SeekOrigin.Begin);
181+
using var reader = new StreamReader(ms);
182+
text = reader.ReadToEnd();
183+
}
184+
185+
var deserialized = Serializer.Deserialize<PersonData>(text, TestJsonContext.Default);
186+
Assert.That(deserialized.FirstName, Is.EqualTo("Bob"));
187+
Assert.That(deserialized.LastName, Is.EqualTo("Builder"));
188+
Assert.That(deserialized.CanFixIt, Is.True);
189+
}
190+
191+
[Test]
192+
public void SerializationWithKVPropertyAttribute()
193+
{
194+
var data = new PersonWithAttributes
195+
{
196+
FirstName = "Jane",
197+
LastName = "Doe",
198+
Secret = "should not appear",
199+
};
200+
201+
string text;
202+
using (var ms = new MemoryStream())
203+
{
204+
Serializer.Serialize(ms, data, "root", TestJsonContext.Default);
205+
ms.Seek(0, SeekOrigin.Begin);
206+
using var reader = new StreamReader(ms);
207+
text = reader.ReadToEnd();
208+
}
209+
210+
Assert.That(text, Does.Contain("first_name"));
211+
Assert.That(text, Does.Contain("last_name"));
212+
Assert.That(text, Does.Not.Contain("Secret"));
213+
214+
var deserialized = Serializer.Deserialize<PersonWithAttributes>(text, TestJsonContext.Default);
215+
Assert.That(deserialized.FirstName, Is.EqualTo("Jane"));
216+
Assert.That(deserialized.LastName, Is.EqualTo("Doe"));
217+
}
218+
219+
#endregion
220+
221+
#region Fallback to reflection for unregistered types
222+
223+
[Test]
224+
public void FallbackToReflectionForUnregisteredType()
225+
{
226+
var result = Serializer.Deserialize<UnregisteredType>(
227+
"\"obj\" { \"Foo\" \"bar\" }",
228+
TestJsonContext.Default);
229+
230+
Assert.That(result.Foo, Is.EqualTo("bar"));
231+
}
232+
233+
#endregion
234+
}
235+
}

ValveKeyValue/ValveKeyValue/KVSerializer.cs

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System.Diagnostics.CodeAnalysis;
2+
using System.Text.Json.Serialization;
23
using ValveKeyValue.Abstraction;
34
using ValveKeyValue.Deserialization;
45
using ValveKeyValue.Deserialization.KeyValues1;
@@ -64,6 +65,24 @@ public KVDocument Deserialize(Stream stream, KVSerializerOptions options = null)
6465
return typedObject;
6566
}
6667

68+
/// <summary>
69+
/// Deserializes an object from a KeyValues representation in a stream, using a source-generated <see cref="JsonSerializerContext"/> for type resolution.
70+
/// </summary>
71+
/// <param name="stream">The stream to deserialize from.</param>
72+
/// <param name="jsonContext">The <see cref="JsonSerializerContext"/> to use for type resolution.</param>
73+
/// <param name="options">Options to use that can influence the deserialization process.</param>
74+
/// <returns>A <typeparamref name="TObject" /> instance representing the KeyValues structure in the stream.</returns>
75+
/// <typeparam name="TObject">The type of object to deserialize.</typeparam>
76+
public TObject Deserialize<[DynamicallyAccessedMembers(Trimming.Constructors | Trimming.Properties)] TObject>(Stream stream, JsonSerializerContext jsonContext, KVSerializerOptions options = null)
77+
{
78+
ArgumentNullException.ThrowIfNull(stream);
79+
ArgumentNullException.ThrowIfNull(jsonContext);
80+
81+
var @object = Deserialize(stream, options ?? KVSerializerOptions.DefaultOptions);
82+
var typedObject = ObjectCopier.MakeObject<TObject>(@object, jsonContext);
83+
return typedObject;
84+
}
85+
6786
/// <summary>
6887
/// Serializes a KeyValue object into stream.
6988
/// </summary>
@@ -116,6 +135,29 @@ public void Serialize(Stream stream, KVDocument data, KVSerializerOptions option
116135
visitor.Visit(name, kvObjectTree);
117136
}
118137

138+
/// <summary>
139+
/// Serializes a KeyValue object into stream in plain text, using a source-generated <see cref="JsonSerializerContext"/> for type resolution.
140+
/// </summary>
141+
/// <param name="stream">The stream to serialize into.</param>
142+
/// <param name="data">The data to serialize.</param>
143+
/// <param name="name">The top-level object name.</param>
144+
/// <param name="jsonContext">The <see cref="JsonSerializerContext"/> to use for type resolution.</param>
145+
/// <param name="options">Options to use that can influence the serialization process.</param>
146+
/// <typeparam name="TData">The type of object to serialize.</typeparam>
147+
public void Serialize<[DynamicallyAccessedMembers(Trimming.Properties)] TData>(Stream stream, TData data, string name, JsonSerializerContext jsonContext, KVSerializerOptions options = null)
148+
{
149+
ArgumentNullException.ThrowIfNull(stream);
150+
ArgumentNullException.ThrowIfNull(data);
151+
ArgumentNullException.ThrowIfNull(name);
152+
ArgumentNullException.ThrowIfNull(jsonContext);
153+
154+
var kvObjectTree = ObjectCopier.FromObject(typeof(TData), data, jsonContext);
155+
156+
using var serializer = MakeSerializer(stream, options ?? KVSerializerOptions.DefaultOptions);
157+
var visitor = new KVObjectVisitor(serializer);
158+
visitor.Visit(name, kvObjectTree);
159+
}
160+
119161
IVisitingReader MakeReader(Stream stream, IParsingVisitationListener listener, KVSerializerOptions options)
120162
{
121163
ArgumentNullException.ThrowIfNull(stream);

0 commit comments

Comments
 (0)