Skip to content

Commit ccaa1ce

Browse files
committed
Added PhpSerializationFilter. Implements #33.
1 parent ea9293f commit ccaa1ce

File tree

5 files changed

+190
-87
lines changed

5 files changed

+190
-87
lines changed

.editorconfig

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,6 @@
1212
csharp_new_line_before_else = false
1313
csharp_new_line_before_open_brace = none
1414
csharp_new_line_before_catch = false
15-
csharp_new_line_before_finally = false
15+
csharp_new_line_before_finally = false
16+
csharp_indent_case_contents = true
17+
csharp_indent_case_contents_when_block = false
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
2+
/**
3+
This Source Code Form is subject to the terms of the Mozilla Public
4+
License, v. 2.0. If a copy of the MPL was not distributed with this
5+
file, You can obtain one at http://mozilla.org/MPL/2.0/.
6+
**/
7+
8+
using System;
9+
using Xunit;
10+
11+
namespace PhpSerializerNET.Test.Serialize;
12+
13+
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)]
14+
public class PhpIgnoreNull : PhpSerializationFilter {
15+
public override string Serialize(object key, object value, PhpSerializiationOptions options) {
16+
if (value != null) {
17+
return PhpSerialization.Serialize(key, options) + PhpSerialization.Serialize(value, options);
18+
}
19+
return null;
20+
}
21+
}
22+
23+
public class IgnoreTestClass {
24+
public string Foo { get; set; }
25+
[PhpIgnoreNull]
26+
public string Bar { get; set; }
27+
}
28+
29+
public class PhpSerializationFilterTest {
30+
[Fact]
31+
public void IgnoreNullIgnoresNull() {
32+
33+
Assert.Equal(
34+
"a:1:{s:3:\"Foo\";N;}",
35+
PhpSerialization.Serialize(
36+
new IgnoreTestClass() { }
37+
)
38+
);
39+
40+
Assert.Equal(
41+
"a:2:{s:3:\"Foo\";N;s:3:\"Bar\";s:3:\"bar\";}",
42+
PhpSerialization.Serialize(
43+
new IgnoreTestClass() { Bar = "bar" }
44+
)
45+
);
46+
}
47+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/**
2+
This Source Code Form is subject to the terms of the Mozilla Public
3+
License, v. 2.0. If a copy of the MPL was not distributed with this
4+
file, You can obtain one at http://mozilla.org/MPL/2.0/.
5+
**/
6+
7+
using System;
8+
9+
namespace PhpSerializerNET;
10+
11+
#nullable enable
12+
13+
/// <summary>
14+
/// Base attribute class for serialization filters. Filters can be used to customize the serialization output
15+
/// on struct and class members. For instance by omitting null values or serializing <see cref="DateTime"/>` as an
16+
/// integer unix timestamp.
17+
/// </summary>
18+
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)]
19+
public abstract class PhpSerializationFilter : Attribute {
20+
21+
public PhpSerializationFilter() {
22+
}
23+
24+
/// <summary>
25+
/// Serialize a class or struct member.
26+
/// </summary>
27+
/// <param name="key"> The key that identifies the member. </param>
28+
/// <param name="value"> The value that is to be serialized. </param>
29+
/// <param name="options"> The options used during the serialization. </param>
30+
/// <returns>
31+
/// The serialized member. e.g. <c>s:3:"foo";i:42;"</c>. <br/>
32+
/// </returns>
33+
/// <remarks>
34+
/// If the default serialization is desired, then return <br/>
35+
/// <c>PhpSerialization.Serialize(key, options) + PhpSerialization.Serialize(value, options);</c>
36+
/// </remarks>
37+
public abstract string? Serialize(object key, object? value, PhpSerializiationOptions options);
38+
}

PhpSerializerNET/Extensions/ArrayExtensions.cs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,8 @@ This Source Code Form is subject to the terms of the Mozilla Public
44
file, You can obtain one at http://mozilla.org/MPL/2.0/.
55
**/
66

7-
using System;
87
using System.Collections.Generic;
98
using System.Reflection;
10-
using System.Text;
119

1210
namespace PhpSerializerNET;
1311

PhpSerializerNET/PhpSerializer.cs renamed to PhpSerializerNET/Serialization/PhpSerializer.cs

Lines changed: 102 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -78,60 +78,60 @@ private string SerializeComplex(object input) {
7878

7979
switch (input) {
8080
case PhpDynamicObject dynamicObject: {
81-
var className = dynamicObject.GetClassName() ?? "stdClass";
82-
ICollection<string> memberNames = dynamicObject.GetDynamicMemberNames();
83-
string preamble = $"O:{className.Length}:\"{className}\":{memberNames.Count}:{{";
84-
string[] entryStrings = new string[memberNames.Count * 2];
85-
int entryIndex = 0;
86-
foreach (var memberName in memberNames) {
87-
entryStrings[entryIndex] = this.Serialize(memberName);
88-
entryStrings[entryIndex + 1] = this.Serialize(dynamicObject.GetMember(memberName));
89-
entryIndex += 2;
90-
}
91-
return string.Concat(preamble, string.Concat(entryStrings), "}");
81+
var className = dynamicObject.GetClassName() ?? "stdClass";
82+
ICollection<string> memberNames = dynamicObject.GetDynamicMemberNames();
83+
string preamble = $"O:{className.Length}:\"{className}\":{memberNames.Count}:{{";
84+
string[] entryStrings = new string[memberNames.Count * 2];
85+
int entryIndex = 0;
86+
foreach (var memberName in memberNames) {
87+
entryStrings[entryIndex] = this.Serialize(memberName);
88+
entryStrings[entryIndex + 1] = this.Serialize(dynamicObject.GetMember(memberName));
89+
entryIndex += 2;
9290
}
91+
return string.Concat(preamble, string.Concat(entryStrings), "}");
92+
}
9393
case ExpandoObject expando: {
94-
var dictionary = (IDictionary<string, object>)expando;
95-
string preamble = $"O:8:\"stdClass\":{dictionary.Keys.Count}:{{";
96-
97-
string[] entryStrings = new string[dictionary.Count * 2];
98-
int entryIndex = 0;
99-
foreach (var entry in dictionary) {
100-
entryStrings[entryIndex] = this.Serialize(entry.Key);
101-
entryStrings[entryIndex + 1] = this.Serialize(entry.Value);
102-
entryIndex += 2;
103-
}
104-
return string.Concat(preamble, string.Concat(entryStrings), "}");
94+
var dictionary = (IDictionary<string, object>)expando;
95+
string preamble = $"O:8:\"stdClass\":{dictionary.Keys.Count}:{{";
96+
97+
string[] entryStrings = new string[dictionary.Count * 2];
98+
int entryIndex = 0;
99+
foreach (var entry in dictionary) {
100+
entryStrings[entryIndex] = this.Serialize(entry.Key);
101+
entryStrings[entryIndex + 1] = this.Serialize(entry.Value);
102+
entryIndex += 2;
105103
}
104+
return string.Concat(preamble, string.Concat(entryStrings), "}");
105+
}
106106
case IDynamicMetaObjectProvider:
107107
throw new NotSupportedException(
108108
"Serialization support for dynamic objects is limited to PhpSerializerNET.PhpDynamicObject and System.Dynamic.ExpandoObject in this version."
109109
);
110110
case IDictionary dictionary: {
111-
string preamble;
112-
if (input is IPhpObject phpObject) {
113-
string className = phpObject.GetClassName();
114-
preamble = $"O:{className.Length}:\"{className}\":{dictionary.Count}:{{";
115-
} else {
116-
var dictionaryType = dictionary.GetType();
117-
if (dictionaryType.GenericTypeArguments.Length > 0) {
118-
var keyType = dictionaryType.GenericTypeArguments[0];
119-
if (!keyType.IsIConvertible() && keyType != typeof(object)) {
120-
throw new Exception($"Can not serialize into associative array with key type {keyType.FullName}");
121-
}
111+
string preamble;
112+
if (input is IPhpObject phpObject) {
113+
string className = phpObject.GetClassName();
114+
preamble = $"O:{className.Length}:\"{className}\":{dictionary.Count}:{{";
115+
} else {
116+
var dictionaryType = dictionary.GetType();
117+
if (dictionaryType.GenericTypeArguments.Length > 0) {
118+
var keyType = dictionaryType.GenericTypeArguments[0];
119+
if (!keyType.IsIConvertible() && keyType != typeof(object)) {
120+
throw new Exception($"Can not serialize into associative array with key type {keyType.FullName}");
122121
}
123-
preamble = $"a:{dictionary.Count}:{{";
124122
}
123+
preamble = $"a:{dictionary.Count}:{{";
124+
}
125125

126-
string[] entryStrings = new string[dictionary.Count * 2];
127-
int entryIndex = 0;
128-
foreach (DictionaryEntry entry in dictionary) {
129-
entryStrings[entryIndex] = this.Serialize(entry.Key);
130-
entryStrings[entryIndex + 1] = this.Serialize(entry.Value);
131-
entryIndex += 2;
132-
}
133-
return string.Concat(preamble, string.Concat(entryStrings), "}");
126+
string[] entryStrings = new string[dictionary.Count * 2];
127+
int entryIndex = 0;
128+
foreach (DictionaryEntry entry in dictionary) {
129+
entryStrings[entryIndex] = this.Serialize(entry.Key);
130+
entryStrings[entryIndex + 1] = this.Serialize(entry.Value);
131+
entryIndex += 2;
134132
}
133+
return string.Concat(preamble, string.Concat(entryStrings), "}");
134+
}
135135
case IList collection:
136136
string[] itemStrings = new string[collection.Count * 2];
137137
for (int i = 0; i < itemStrings.Length; i += 2) {
@@ -144,45 +144,52 @@ private string SerializeComplex(object input) {
144144
"}"
145145
);
146146
default: {
147-
StringBuilder output = new StringBuilder();
148-
var inputType = input.GetType();
147+
StringBuilder output = new StringBuilder();
148+
var inputType = input.GetType();
149149

150-
if (typeof(IPhpObject).IsAssignableFrom(inputType) || inputType.GetCustomAttribute<PhpClass>() != null) {
151-
return this.SerializeToObject(input);
152-
}
150+
if (typeof(IPhpObject).IsAssignableFrom(inputType) || inputType.GetCustomAttribute<PhpClass>() != null) {
151+
return this.SerializeToObject(input);
152+
}
153153

154-
List<MemberInfo> members = new();
155-
if (inputType.IsValueType) {
156-
foreach (FieldInfo field in inputType.GetFields()) {
157-
if (field.IsPublic) {
158-
var attribute = Attribute.GetCustomAttribute(field, typeof(PhpIgnoreAttribute), false);
159-
if (attribute == null) {
160-
members.Add(field);
161-
}
154+
List<MemberInfo> members = new();
155+
if (inputType.IsValueType) {
156+
foreach (FieldInfo field in inputType.GetFields()) {
157+
if (field.IsPublic) {
158+
var attribute = Attribute.GetCustomAttribute(field, typeof(PhpIgnoreAttribute), false);
159+
if (attribute == null) {
160+
members.Add(field);
162161
}
163162
}
164-
} else {
165-
foreach (PropertyInfo property in inputType.GetProperties()) {
166-
if (property.CanRead) {
167-
var ignoreAttribute = Attribute.GetCustomAttribute(
168-
property,
169-
typeof(PhpIgnoreAttribute),
170-
false
171-
);
172-
if (ignoreAttribute == null) {
173-
members.Add(property);
174-
}
163+
}
164+
} else {
165+
foreach (PropertyInfo property in inputType.GetProperties()) {
166+
if (property.CanRead) {
167+
var ignoreAttribute = Attribute.GetCustomAttribute(
168+
property,
169+
typeof(PhpIgnoreAttribute),
170+
false
171+
);
172+
if (ignoreAttribute == null) {
173+
members.Add(property);
175174
}
176175
}
177176
}
178-
output.Append($"a:{members.Count}:");
179-
output.Append('{');
180-
foreach (var member in members) {
181-
output.Append(this.SerializeMember(member, input));
177+
}
178+
int memberCount = 0;
179+
StringBuilder memberData = new();
180+
foreach (var member in members) {
181+
var memberString = this.SerializeMember(member, input);
182+
if (memberString != null) {
183+
memberData.Append(memberString);
184+
memberCount++;
182185
}
183-
output.Append('}');
184-
return output.ToString();
185186
}
187+
output.Append($"a:{memberCount}:")
188+
.Append('{')
189+
.Append(memberData)
190+
.Append('}');
191+
return output.ToString();
192+
}
186193
}
187194
}
188195

@@ -211,18 +218,24 @@ private string SerializeToObject(object input) {
211218
}
212219
}
213220
}
214-
221+
int memberCount = 0;
222+
StringBuilder members = new StringBuilder();
223+
foreach (PropertyInfo property in properties) {
224+
var memberString = this.SerializeMember(property, input);
225+
if (memberString != null) {
226+
members.Append(memberString);
227+
memberCount++;
228+
}
229+
}
215230
output.Append("O:")
216231
.Append(className.Length)
217232
.Append(":\"")
218233
.Append(className)
219234
.Append("\":")
220235
.Append(properties.Count)
221-
.Append(":{");
222-
foreach (PropertyInfo property in properties) {
223-
output.Append(this.SerializeMember(property, input));
224-
}
225-
output.Append('}');
236+
.Append(":{")
237+
.Append(members)
238+
.Append('}');
226239
return output.ToString();
227240
}
228241

@@ -232,13 +245,18 @@ private string SerializeMember(MemberInfo member, object input) {
232245
typeof(PhpPropertyAttribute),
233246
false
234247
);
235-
248+
object key;
236249
if (attribute != null) {
237-
if (attribute.IsInteger == true) {
238-
return $"{this.Serialize(attribute.Key)}{this.Serialize(member.GetValue(input))}";
239-
}
240-
return $"{this.Serialize(attribute.Name)}{this.Serialize(member.GetValue(input))}";
250+
key = attribute.IsInteger
251+
? attribute.Key
252+
: attribute.Name;
253+
} else {
254+
key = member.Name;
255+
}
256+
var filter = member.GetCustomAttribute<PhpSerializationFilter>();
257+
if (filter != null) {
258+
return filter.Serialize(key, member.GetValue(input), this._options);
241259
}
242-
return $"{this.Serialize(member.Name)}{this.Serialize(member.GetValue(input))}";
260+
return string.Concat(this.Serialize(key), this.Serialize(member.GetValue(input)));
243261
}
244262
}

0 commit comments

Comments
 (0)