Skip to content

Commit 93be0d6

Browse files
committed
Implement PhpProperty(int).
This allows to serialize and deserialize arrays with mixed key types (integer and string). TODO: Documentation.
1 parent d788bbe commit 93be0d6

File tree

11 files changed

+171
-20
lines changed

11 files changed

+171
-20
lines changed
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
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+
8+
namespace PhpSerializerNET.Test.DataTypes {
9+
public class MixedKeysObject {
10+
[PhpProperty(0)]
11+
public string Foo { get; set; }
12+
[PhpProperty(1)]
13+
public string Bar { get; set; }
14+
[PhpProperty("a")]
15+
public string Baz { get; set; }
16+
[PhpProperty("b")]
17+
public string Dummy { get; set; }
18+
}
19+
20+
[PhpClass]
21+
public class MixedKeysPhpClass {
22+
[PhpProperty(0)]
23+
public string Foo { get; set; }
24+
[PhpProperty(1)]
25+
public string Bar { get; set; }
26+
[PhpProperty("a")]
27+
public string Baz { get; set; }
28+
[PhpProperty("b")]
29+
public string Dummy { get; set; }
30+
}
31+
}

PhpSerializerNET.Test/Deserialize/ArrayDeserialization.cs

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,7 @@ public void ExplicitToList() {
167167
public void ExplicitToArray() {
168168
var result = PhpSerialization.Deserialize<string[]>("a:3:{i:0;s:5:\"Hello\";i:1;s:5:\"World\";i:2;i:12345;}");
169169

170-
CollectionAssert.AreEqual(new string[]{ "Hello", "World", "12345" }, result);
170+
CollectionAssert.AreEqual(new string[] { "Hello", "World", "12345" }, result);
171171
}
172172

173173
[TestMethod]
@@ -186,7 +186,7 @@ public void ExplicitToEmptyList() {
186186
}
187187

188188
[TestMethod]
189-
public void ImplicitToDictionary(){
189+
public void ImplicitToDictionary() {
190190
var result = PhpSerialization.Deserialize(
191191
"a:5:{s:7:\"AString\";s:22:\"this is a string value\";s:9:\"AnInteger\";i:10;s:7:\"ADouble\";d:1.2345;s:4:\"True\";b:1;s:5:\"False\";b:0;}"
192192
);
@@ -234,5 +234,17 @@ public void Test_Issue12() {
234234
var result = PhpSerialization.Deserialize("a:1:{i:0;a:4:{s:1:\"A\";s:2:\"63\";s:1:\"B\";a:2:{i:558710;s:1:\"2\";i:558709;s:1:\"2\";}s:1:\"C\";s:2:\"71\";s:1:\"G\";a:3:{s:1:\"x\";s:6:\"446368\";s:1:\"y\";s:1:\"0\";s:1:\"z\";s:5:\"1.029\";}}}");
235235
Assert.IsNotNull(result);
236236
}
237+
238+
[TestMethod]
239+
public void MixedKeyArrayIntoObject() {
240+
var result = PhpSerialization.Deserialize<MixedKeysObject>(
241+
"a:4:{i:0;s:3:\"Foo\";i:1;s:3:\"Bar\";s:1:\"a\";s:1:\"A\";s:1:\"b\";s:1:\"B\";}"
242+
);
243+
244+
Assert.AreEqual("Foo", result.Foo);
245+
Assert.AreEqual("Bar", result.Bar);
246+
Assert.AreEqual("A", result.Baz);
247+
Assert.AreEqual("B", result.Dummy);
248+
}
237249
}
238250
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
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 Microsoft.VisualStudio.TestTools.UnitTesting;
9+
using PhpSerializerNET.Test.DataTypes;
10+
11+
namespace PhpSerializerNET.Test.Deserialize {
12+
[TestClass]
13+
public class ObjectDeserializationTest {
14+
[TestMethod]
15+
public void IntegerKeysClass() {
16+
var result = PhpSerialization.Deserialize<MixedKeysPhpClass>(
17+
"O:8:\"stdClass\":4:{i:0;s:3:\"Foo\";i:1;s:3:\"Bar\";s:1:\"a\";s:1:\"A\";s:1:\"b\";s:1:\"B\";}"
18+
);
19+
20+
Assert.IsNotNull(result);
21+
Assert.AreEqual("Foo", result.Foo);
22+
Assert.AreEqual("Bar", result.Bar);
23+
Assert.AreEqual("A", result.Baz);
24+
Assert.AreEqual("B", result.Dummy);
25+
}
26+
}
27+
}

PhpSerializerNET.Test/Deserialize/Validation/TestOtherErrors.cs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ This Source Code Form is subject to the terms of the Mozilla Public
66

77
using System;
88
using Microsoft.VisualStudio.TestTools.UnitTesting;
9+
using PhpSerializerNET.Test.DataTypes;
910

1011
namespace PhpSerializerNET.Test.Deserialize.Validation {
1112
[TestClass]
@@ -52,5 +53,25 @@ public void ErrorOnEmptyInput() {
5253

5354
Assert.AreEqual(expected, ex.Message);
5455
}
56+
57+
58+
[TestMethod]
59+
public void ThrowOnIllegalKeyType() {
60+
var ex = Assert.ThrowsException<DeserializationException>(
61+
() => PhpSerialization.Deserialize<MyPhpObject>("O:8:\"stdClass\":1:{b:1;s:4:\"true\";}")
62+
);
63+
Assert.AreEqual(
64+
"Error encountered deserizalizing an object of type 'PhpSerializerNET.Test.DataTypes.MyPhpObject': " +
65+
"The key '1' (from the token at position 18) has an unsupported type of 'Boolean'.",
66+
ex.Message
67+
);
68+
}
69+
70+
[TestMethod]
71+
public void ThrowOnIntegerKeyPhpObject() {
72+
var ex = Assert.ThrowsException<ArgumentException>(
73+
() => PhpSerialization.Deserialize<PhpObjectDictionary>("O:8:\"stdClass\":1:{i:0;s:4:\"true\";}")
74+
);
75+
}
5576
}
5677
}

PhpSerializerNET.Test/Serialize/ArraySerialization.cs

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,36 @@ 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.Collections.Generic;
78
using Microsoft.VisualStudio.TestTools.UnitTesting;
9+
using PhpSerializerNET.Test.DataTypes;
810

911
namespace PhpSerializerNET.Test.Serialize {
1012
[TestClass]
1113
public class ArraySerialization {
1214
[TestMethod]
1315
public void StringArraySerializaton() {
14-
string[] data = new string[3] {"a", "b", "c"};
16+
string[] data = new string[3] { "a", "b", "c" };
1517

1618
Assert.AreEqual(
1719
"a:3:{i:0;s:1:\"a\";i:1;s:1:\"b\";i:2;s:1:\"c\";}",
1820
PhpSerialization.Serialize(data)
1921
);
2022
}
23+
24+
[TestMethod]
25+
public void ObjectIntoMixedKeyArray() {
26+
var data = new MixedKeysObject() {
27+
Foo = "Foo",
28+
Bar = "Bar",
29+
Baz = "A",
30+
Dummy = "B",
31+
};
32+
33+
Assert.AreEqual(
34+
"a:4:{i:0;s:3:\"Foo\";i:1;s:3:\"Bar\";s:1:\"a\";s:1:\"A\";s:1:\"b\";s:1:\"B\";}",
35+
PhpSerialization.Serialize(data)
36+
);
37+
}
2138
}
2239
}

PhpSerializerNET.Test/Serialize/ObjectSerialization.cs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,5 +61,20 @@ public void SerializeObjectToObject() {
6161
PhpSerialization.Serialize(testObject)
6262
);
6363
}
64+
65+
[TestMethod]
66+
public void ObjectIntoMixedKeyArray() {
67+
var data = new MixedKeysPhpClass() {
68+
Foo = "Foo",
69+
Bar = "Bar",
70+
Baz = "A",
71+
Dummy = "B",
72+
};
73+
74+
Assert.AreEqual(
75+
"O:8:\"stdClass\":4:{i:0;s:3:\"Foo\";i:1;s:3:\"Bar\";s:1:\"a\";s:1:\"A\";s:1:\"b\";s:1:\"B\";}",
76+
PhpSerialization.Serialize(data)
77+
);
78+
}
6479
}
6580
}

PhpSerializerNET/Attributes/PhpProperty.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,20 @@ namespace PhpSerializerNET {
1111
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)]
1212
public class PhpPropertyAttribute : Attribute {
1313
public string Name { get; set; }
14+
public long Key { get; set; }
15+
public bool IsInteger { get; private set; } = false;
1416

1517
public PhpPropertyAttribute(string name) {
1618
this.Name = name;
1719
}
20+
21+
/// <summary>
22+
/// Define an integer key for a given property.
23+
/// Note: This also affects serialization into object notation, as that is a legal way of representing an object.
24+
/// </summary>
25+
public PhpPropertyAttribute(long key) {
26+
this.Key = key;
27+
this.IsInteger = true;
28+
}
1829
}
1930
}

PhpSerializerNET/Extensions/ArrayExtensions.cs

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,8 @@ public static string Utf8Substring(this byte[] array, int start, int length, Enc
2929
}
3030
}
3131

32-
public static Dictionary<string, PropertyInfo> GetAllProperties(this PropertyInfo[] properties, PhpDeserializationOptions options) {
33-
var result = new Dictionary<string, PropertyInfo>(properties.Length);
32+
public static Dictionary<object, PropertyInfo> GetAllProperties(this PropertyInfo[] properties, PhpDeserializationOptions options) {
33+
var result = new Dictionary<object, PropertyInfo>(properties.Length);
3434
foreach (var property in properties) {
3535
var isIgnored = false;
3636
var attributes = PhpPropertyAttribute.GetCustomAttributes(property, false);
@@ -45,10 +45,14 @@ public static Dictionary<string, PropertyInfo> GetAllProperties(this PropertyInf
4545
}
4646
}
4747
if (phpPropertyAttribute != null) {
48-
var attributeName = options.CaseSensitiveProperties
49-
? phpPropertyAttribute.Name
50-
: phpPropertyAttribute.Name.ToLower();
51-
result.Add(attributeName, isIgnored ? null : property);
48+
if (phpPropertyAttribute.IsInteger) {
49+
result.Add(phpPropertyAttribute.Key, isIgnored ? null : property);
50+
} else {
51+
var attributeName = options.CaseSensitiveProperties
52+
? phpPropertyAttribute.Name
53+
: phpPropertyAttribute.Name.ToLower();
54+
result.Add(attributeName, isIgnored ? null : property);
55+
}
5256
}
5357
var propertyName = options.CaseSensitiveProperties
5458
? property.Name

PhpSerializerNET/PhpDeserializer.cs

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ internal class PhpDeserializer {
2121
};
2222
private static readonly object TypeLookupCacheSyncObject = new();
2323

24-
private static readonly Dictionary<Type, Dictionary<string, PropertyInfo>> PropertyInfoCache = new();
24+
private static readonly Dictionary<Type, Dictionary<object, PropertyInfo>> PropertyInfoCache = new();
2525
private static readonly object PropertyInfoCacheSyncObject = new();
2626

2727
private static Dictionary<Type, Dictionary<string, FieldInfo>> FieldInfoCache { get; set; } = new();
@@ -103,8 +103,7 @@ private object MakeClass(PhpSerializeToken token) {
103103
object constructedObject;
104104
Type targetType = null;
105105
if (typeName != "stdClass" && this._options.EnableTypeLookup) {
106-
lock (TypeLookupCacheSyncObject)
107-
{
106+
lock (TypeLookupCacheSyncObject) {
108107
if (TypeLookupCache.ContainsKey(typeName)) {
109108
targetType = TypeLookupCache[typeName];
110109
} else {
@@ -393,7 +392,7 @@ private object MakeStruct(Type targetType, PhpSerializeToken token) {
393392

394393
private object MakeObject(Type targetType, PhpSerializeToken token) {
395394
var result = Activator.CreateInstance(targetType);
396-
Dictionary<string, PropertyInfo> properties;
395+
Dictionary<object, PropertyInfo> properties;
397396
lock (PropertyInfoCacheSyncObject) {
398397
if (PropertyInfoCache.ContainsKey(targetType)) {
399398
properties = PropertyInfoCache[targetType];
@@ -406,7 +405,18 @@ private object MakeObject(Type targetType, PhpSerializeToken token) {
406405
}
407406

408407
for (int i = 0; i < token.Children.Length; i += 2) {
409-
var propertyName = this._options.CaseSensitiveProperties ? token.Children[i].Value : token.Children[i].Value.ToLower();
408+
object propertyName;
409+
if (token.Children[i].Type == PhpSerializerType.String) {
410+
propertyName = this._options.CaseSensitiveProperties ? token.Children[i].Value : token.Children[i].Value.ToLower();
411+
} else if (token.Children[i].Type == PhpSerializerType.Integer) {
412+
propertyName = token.Children[i].ToLong();
413+
} else {
414+
throw new DeserializationException(
415+
$"Error encountered deserizalizing an object of type '{targetType.FullName}': " +
416+
$"The key '{token.Children[i].Value}' (from the token at position {token.Children[i].Position}) has an unsupported type of '{token.Children[i].Type}'."
417+
);
418+
}
419+
410420
var valueToken = token.Children[i + 1];
411421

412422
if (!properties.ContainsKey(propertyName)) {

PhpSerializerNET/PhpSerializer.cs

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -242,17 +242,20 @@ private string SerializeToObject(object input) {
242242
return output.ToString();
243243
}
244244

245-
private string SerializeMember(MemberInfo member, object input) {
245+
private string SerializeMember(MemberInfo member, object input, bool isObjectMember = false) {
246246
PhpPropertyAttribute attribute = (PhpPropertyAttribute)Attribute.GetCustomAttribute(
247247
member,
248248
typeof(PhpPropertyAttribute),
249249
false
250250
);
251251

252-
var propertyName = attribute != null
253-
? attribute.Name
254-
: member.Name;
255-
return $"{this.Serialize(propertyName)}{this.Serialize(member.GetValue(input))}";
252+
if (attribute != null) {
253+
if (attribute.IsInteger == true) {
254+
return $"{this.Serialize(attribute.Key)}{this.Serialize(member.GetValue(input))}";
255+
}
256+
return $"{this.Serialize(attribute.Name)}{this.Serialize(member.GetValue(input))}";
257+
}
258+
return $"{this.Serialize(member.Name)}{this.Serialize(member.GetValue(input))}";
256259
}
257260
}
258261
}

0 commit comments

Comments
 (0)