stringepsilon / phpserializernet Goto Github PK
View Code? Open in Web Editor NEWA .NET library for working with the PHP serialization format
License: Mozilla Public License 2.0
A .NET library for working with the PHP serialization format
License: Mozilla Public License 2.0
There are still some tests floating around that need sorting into the proper folder structure:
Some tests in those files might also be redundant to other tests written in the meantime or significantly overlap in scope.
Perhaps gated behind an option, but very useful.
Hiya,
Thanks for your last update.
Unfortunately it seems like it broke something. Parsing the sample below works in 7.0.0 but not in 7.0.1!
a:1:{i:0;a:11:{s:14:"content_length";s:2:"63";s:13:"content_width";s:2:"63";s:14:"content_height";s:3:"2.3";s:14:"content_weight";s:5:"7.913";s:9:"belt_size";s:5:"221.6";s:5:"items";a:4:{i:558710;s:1:"2";i:558709;s:1:"2";i:558708;s:1:"3";i:558711;s:1:"2";}s:6:"length";s:2:"71";s:5:"width";s:2:"68";s:6:"height";s:3:"7.3";s:6:"weight";s:5:"8.942";s:9:"packaging";a:3:{s:2:"id";s:6:"446368";s:4:"cost";s:1:"0";s:6:"weight";s:5:"1.029";}}}
Array
(
[0] => Array
(
[content_length] => 63
[content_width] => 63
[content_height] => 2.3
[content_weight] => 7.913
[belt_size] => 221.6
[items] => Array
(
[558710] => 2
[558709] => 2
[558708] => 3
[558711] => 2
)
[length] => 71
[width] => 68
[height] => 7.3
[weight] => 8.942
[packaging] => Array
(
[id] => 446368
[cost] => 0
[weight] => 1.029
)
)
)
Seems like maybe the pos is incemented too much looking at your last change?
Hello,
I'd love being able to debug the library on the fly in my app and the process seems simple enough.
https://docs.microsoft.com/en-us/nuget/create-packages/symbol-packages-snupkg
https://github.com/dotnet/sourcelink/blob/main/README.md
The commandline option seems to work fine to generate a symbols package.
PM> cd PhpSerializerNET
PM> dotnet pack PhpSerializerNET.csproj -p:IncludeSymbols=true -p:SymbolPackageFormat=snupkg
Microsoft (R) Build Engine version 17.0.0+c9eb9dd64 for .NET
Copyright (C) Microsoft Corporation. All rights reserved.
Determining projects to restore...
All projects are up-to-date for restore.
PhpSerializerNET -> C:\Repos\PhpSerializerNET\PhpSerializerNET\bin\Debug\net6.0\PhpSerializerNET.dll
Successfully created package 'C:\Repos\PhpSerializerNET\PhpSerializerNET\bin\Debug\PhpSerializerNET.0.9.0.nupkg'.
Successfully created package 'C:\Repos\PhpSerializerNET\PhpSerializerNET\bin\Debug\PhpSerializerNET.0.9.0.snupkg'.
Seems like you can also embed the dbg symbols which would be easier, but that would bump the package size a bit, hence why a seperate symbols package is recommended.
Note that symbol package isn't the only strategy to make the debug symbols available to the consumers of your library. It's also possible to embed them in the dll or exe with the following project property: embedded
Hiya,
Got another bug after tying the empty string to default feature you introduced for me.
parsing:
a:1:{i:0;s:0:"";}
To
List<string>
Funnily enough Activator.CreateInstance<string>()
apperently does not work, since strings are immutable and cannot be created.
Also right now Activator.CreateInstance
creates a new type - this is not really the 'default' since the defautl for a reference type is null. That's just a remark i can deal with new or default (but not a crash like it does now).
Also see:
https://stackoverflow.com/a/353073/4122889
I think the same also applies to Guid
- but i could use a nullable there and this should work fine as-is.. Will have to test that later.
I think we can add an exception for string now, e.g.
if (token.Value == "" && this._options.EmptyStringToDefault){
if(targetType == typeof(string))
return null;
// Something for Guid?
return Activator.CreateInstance(targetType);
}
Or we can return null for all reference types (like the SO does)
Using .net6 + v7.0.2 (latest)
Hiya,
I have a model setup to parse the following string:
public class TaxesPhpModel
{
/// <summary>
/// Index seems to be either 1 or 32?
/// </summary>
[PhpProperty("total")]
public Dictionary<long, double> Total { get; set; }
}
var str5 = "a:1:{s:5:\"total\";a:1:{i:1;s:0:\"\";}}";
var res5 = PhpSerialization.Deserialize<TaxesPhpModel>(str5, new PhpDeserializationOptions()
{
AllowExcessKeys = true
});
Which deals with strings like:
a:1:{s:5:"total";a:1:{i:1;s:6:"1.3205";}}
And
a:1:{s:5:"total";a:1:{i:32;s:6:"1.3205";}}
(for some reason i get integers 1 or 32 for the array... man php is weird)
But now it throws errors for the following string:
a:1:{s:5:"total";a:1:{i:1;s:0:"";}}
Array
(
[total] => Array
(
[1] =>
)
)
Because total is an empty string, which it cannot assign to a value. I've tried double
and double?
.
This works for Dictionary<string, string>
or Dictionary<long, string>
.
It would be nice if i could get the default value for an empty string so i'm not forced to use string rather than double.
net core 5 v7.0.0
Test sample:
public class TaxesPhpModel
{
/// <summary>
/// Index seems to be either 1 or 32?
/// </summary>
[PhpProperty("total")]
public Dictionary<long, double> Total { get; set; }
}
public class TaxesPhpModel2
{
/// <summary>
/// Index seems to be either 1 or 32?
/// </summary>
[PhpProperty("total")]
public Dictionary<long, string> Total { get; set; }
}
public class TaxesPhpModel3
{
/// <summary>
/// Index seems to be either 1 or 32?
/// </summary>
[PhpProperty("total")]
public Dictionary<string, string> Total { get; set; }
}
class Program
{
static void Main(string[] args)
{
var str5 = "a:1:{s:5:\"total\";a:1:{i:1;s:0:\"\";}}";
var phpDeserializationOptions = new PhpDeserializationOptions()
{
AllowExcessKeys = true
};
var res4 = PhpSerialization.Deserialize(str5); // Works
var res3 = PhpSerialization.Deserialize<TaxesPhpModel3>(str5, phpDeserializationOptions); // Works
var res2 = PhpSerialization.Deserialize<TaxesPhpModel2>(str5, phpDeserializationOptions); // Doesnt work
var res = PhpSerialization.Deserialize<TaxesPhpModel>(str5, phpDeserializationOptions); // Doesnt work
}
}
Failing test:
[TestMethod]
public void ExplicitToArray() {
var result = PhpSerialization.Deserialize<string[]>("a:3:{i:0;s:5:\"Hello\";i:1;s:5:\"World\";i:2;i:12345;}");
CollectionAssert.AreEqual(new string[]{ "Hello", "World", "12345" }, result);
}
Basically: Support object[] and so on.
Will get to updating as soon as my linux distro packaged dotnet 8. Just adding this issue as a reminder for myself.
Failing test:
public class MyPhpObject : IPhpObject {
public string GetClassName() => "MyPhpObject";
public void SetClassName(string className) {};
public string Foo {get;set;}
}
[TestMethod]
public void SerializeIPhpObject() {
Assert.AreEqual( // strings:
"O:11:\"MyPhpObject\":{s:3:\"Foo\";s:0:\"\"}",
PhpSerialization.Serialize( new MyPhpObject() {Foo =""})
);
}
Noticed this while writing the documentation for the interface.
I'm looking to have an object that I'm using as a data model which will eventually be serialized. But I want to mark some fields as ignored, but not PhpIgnore
(which skips it unconditionally) but something along the lines of "Ignore, but only if it's null".
For example:
public class MyClass {
[PhpProperty("Foo")]
public string Foo {get;set;};
// ... Other stuff
}
If Foo
is null
, it will serialize along the lines of: s:3:"Foo";N;
I would like this output to be skipped entirely if it is null.
Maybe something like
public class MyClass {
[PhpProperty("Foo")]
[PhpIgnoreNull] // Or similar
public string Foo {get;set;};
// ... Other stuff
}
Hiya,
I just encountered something weird.
I'm parsing this string which has different objects:
a:11:{i:0;O:31:"KPS\Logistics\Status\To\Delayed":5:{s:12:"delay_reason";s:12:"out_of_stock";s:11:"delay_until";O:8:"DateTime":3:{s:4:"date";s:26:"2021-10-28 00:00:00.000000";s:13:"timezone_type";i:1;s:8:"timezone";s:6:"+00:00";}s:4:"from";s:7:"delayed";s:2:"to";s:7:"delayed";s:4:"date";O:8:"DateTime":3:{s:4:"date";s:26:"2021-10-04 07:57:02.880620";s:13:"timezone_type";i:3;s:8:"timezone";s:16:"Europe/Amsterdam";}}i:1;O:31:"KPS\Logistics\Status\To\Regular":3:{s:4:"from";s:10:"processing";s:2:"to";s:10:"processing";s:4:"date";O:8:"DateTime":3:{s:4:"date";s:26:"2021-10-28 06:59:16.556841";s:13:"timezone_type";i:3;s:8:"timezone";s:16:"Europe/Amsterdam";}}i:2;O:31:"KPS\Logistics\Status\To\Delayed":5:{s:12:"delay_reason";s:12:"out_of_stock";s:11:"delay_until";O:8:"DateTime":3:{s:4:"date";s:26:"2021-11-25 00:00:00.000000";s:13:"timezone_type";i:1;s:8:"timezone";s:6:"+00:00";}s:4:"from";s:7:"delayed";s:2:"to";s:7:"delayed";s:4:"date";O:8:"DateTime":3:{s:4:"date";s:26:"2021-10-28 08:54:25.005632";s:13:"timezone_type";i:3;s:8:"timezone";s:16:"Europe/Amsterdam";}}i:3;O:31:"KPS\Logistics\Status\To\Regular":3:{s:4:"from";s:10:"processing";s:2:"to";s:10:"processing";s:4:"date";O:8:"DateTime":3:{s:4:"date";s:26:"2021-10-28 08:54:25.037250";s:13:"timezone_type";i:3;s:8:"timezone";s:16:"Europe/Amsterdam";}}i:4;O:31:"KPS\Logistics\Status\To\Delayed":5:{s:12:"delay_reason";s:12:"out_of_stock";s:11:"delay_until";O:8:"DateTime":3:{s:4:"date";s:26:"2021-11-25 00:00:00.000000";s:13:"timezone_type";i:1;s:8:"timezone";s:6:"+00:00";}s:4:"from";s:7:"delayed";s:2:"to";s:7:"delayed";s:4:"date";O:8:"DateTime":3:{s:4:"date";s:26:"2021-10-28 11:25:48.219544";s:13:"timezone_type";i:3;s:8:"timezone";s:16:"Europe/Amsterdam";}}i:5;O:31:"KPS\Logistics\Status\To\Regular":3:{s:4:"from";s:10:"processing";s:2:"to";s:10:"processing";s:4:"date";O:8:"DateTime":3:{s:4:"date";s:26:"2021-11-25 07:04:58.147908";s:13:"timezone_type";i:3;s:8:"timezone";s:16:"Europe/Amsterdam";}}i:6;O:31:"KPS\Logistics\Status\To\Delayed":5:{s:12:"delay_reason";s:12:"out_of_stock";s:11:"delay_until";O:8:"DateTime":3:{s:4:"date";s:26:"2021-12-24 00:00:00.000000";s:13:"timezone_type";i:1;s:8:"timezone";s:6:"+00:00";}s:4:"from";s:7:"delayed";s:2:"to";s:7:"delayed";s:4:"date";O:8:"DateTime":3:{s:4:"date";s:26:"2021-11-25 13:50:07.229584";s:13:"timezone_type";i:3;s:8:"timezone";s:16:"Europe/Amsterdam";}}i:7;O:31:"KPS\Logistics\Status\To\Regular":3:{s:4:"from";s:10:"processing";s:2:"to";s:10:"processing";s:4:"date";O:8:"DateTime":3:{s:4:"date";s:26:"2021-11-25 13:50:07.262880";s:13:"timezone_type";i:3;s:8:"timezone";s:16:"Europe/Amsterdam";}}i:8;O:31:"KPS\Logistics\Status\To\Delayed":5:{s:12:"delay_reason";s:12:"out_of_stock";s:11:"delay_until";O:8:"DateTime":3:{s:4:"date";s:26:"2021-12-24 00:00:00.000000";s:13:"timezone_type";i:1;s:8:"timezone";s:6:"+00:00";}s:4:"from";s:7:"delayed";s:2:"to";s:7:"delayed";s:4:"date";O:8:"DateTime":3:{s:4:"date";s:26:"2021-11-25 13:50:16.825377";s:13:"timezone_type";i:3;s:8:"timezone";s:16:"Europe/Amsterdam";}}i:9;O:31:"KPS\Logistics\Status\To\Regular":3:{s:4:"from";s:8:"canceled";s:2:"to";s:8:"canceled";s:4:"date";O:8:"DateTime":3:{s:4:"date";s:26:"2021-11-26 09:06:38.775093";s:13:"timezone_type";i:3;s:8:"timezone";s:16:"Europe/Amsterdam";}}i:10;O:32:"KPS\Logistics\Status\To\Canceled":4:{s:2:"to";s:8:"canceled";s:13:"cancel_reason";s:6:"refund";s:4:"from";s:8:"canceled";s:4:"date";O:8:"DateTime":3:{s:4:"date";s:26:"2021-11-26 08:06:38.796815";s:13:"timezone_type";i:3;s:8:"timezone";s:3:"UTC";}}}
Array
(
[0] => __PHP_Incomplete_Class Object
(
[__PHP_Incomplete_Class_Name] => KPS\Logistics\Status\To\Delayed
[delay_reason] => out_of_stock
[delay_until] => DateTime Object
(
[date] => 2021-10-28 00:00:00.000000
[timezone_type] => 1
[timezone] => +00:00
)
[from] => delayed
[to] => delayed
[date] => DateTime Object
(
[date] => 2021-10-04 07:57:02.880620
[timezone_type] => 3
[timezone] => Europe/Amsterdam
)
)
[1] => __PHP_Incomplete_Class Object
(
[__PHP_Incomplete_Class_Name] => KPS\Logistics\Status\To\Regular
[from] => processing
[to] => processing
[date] => DateTime Object
(
[date] => 2021-10-28 06:59:16.556841
[timezone_type] => 3
[timezone] => Europe/Amsterdam
)
)
[2] => __PHP_Incomplete_Class Object
(
[__PHP_Incomplete_Class_Name] => KPS\Logistics\Status\To\Delayed
[delay_reason] => out_of_stock
[delay_until] => DateTime Object
(
[date] => 2021-11-25 00:00:00.000000
[timezone_type] => 1
[timezone] => +00:00
)
[from] => delayed
[to] => delayed
[date] => DateTime Object
(
[date] => 2021-10-28 08:54:25.005632
[timezone_type] => 3
[timezone] => Europe/Amsterdam
)
)
[3] => __PHP_Incomplete_Class Object
(
[__PHP_Incomplete_Class_Name] => KPS\Logistics\Status\To\Regular
[from] => processing
[to] => processing
[date] => DateTime Object
(
[date] => 2021-10-28 08:54:25.037250
[timezone_type] => 3
[timezone] => Europe/Amsterdam
)
)
[4] => __PHP_Incomplete_Class Object
(
[__PHP_Incomplete_Class_Name] => KPS\Logistics\Status\To\Delayed
[delay_reason] => out_of_stock
[delay_until] => DateTime Object
(
[date] => 2021-11-25 00:00:00.000000
[timezone_type] => 1
[timezone] => +00:00
)
[from] => delayed
[to] => delayed
[date] => DateTime Object
(
[date] => 2021-10-28 11:25:48.219544
[timezone_type] => 3
[timezone] => Europe/Amsterdam
)
)
[5] => __PHP_Incomplete_Class Object
(
[__PHP_Incomplete_Class_Name] => KPS\Logistics\Status\To\Regular
[from] => processing
[to] => processing
[date] => DateTime Object
(
[date] => 2021-11-25 07:04:58.147908
[timezone_type] => 3
[timezone] => Europe/Amsterdam
)
)
[6] => __PHP_Incomplete_Class Object
(
[__PHP_Incomplete_Class_Name] => KPS\Logistics\Status\To\Delayed
[delay_reason] => out_of_stock
[delay_until] => DateTime Object
(
[date] => 2021-12-24 00:00:00.000000
[timezone_type] => 1
[timezone] => +00:00
)
[from] => delayed
[to] => delayed
[date] => DateTime Object
(
[date] => 2021-11-25 13:50:07.229584
[timezone_type] => 3
[timezone] => Europe/Amsterdam
)
)
[7] => __PHP_Incomplete_Class Object
(
[__PHP_Incomplete_Class_Name] => KPS\Logistics\Status\To\Regular
[from] => processing
[to] => processing
[date] => DateTime Object
(
[date] => 2021-11-25 13:50:07.262880
[timezone_type] => 3
[timezone] => Europe/Amsterdam
)
)
[8] => __PHP_Incomplete_Class Object
(
[__PHP_Incomplete_Class_Name] => KPS\Logistics\Status\To\Delayed
[delay_reason] => out_of_stock
[delay_until] => DateTime Object
(
[date] => 2021-12-24 00:00:00.000000
[timezone_type] => 1
[timezone] => +00:00
)
[from] => delayed
[to] => delayed
[date] => DateTime Object
(
[date] => 2021-11-25 13:50:16.825377
[timezone_type] => 3
[timezone] => Europe/Amsterdam
)
)
[9] => __PHP_Incomplete_Class Object
(
[__PHP_Incomplete_Class_Name] => KPS\Logistics\Status\To\Regular
[from] => canceled
[to] => canceled
[date] => DateTime Object
(
[date] => 2021-11-26 09:06:38.775093
[timezone_type] => 3
[timezone] => Europe/Amsterdam
)
)
[10] => __PHP_Incomplete_Class Object
(
[__PHP_Incomplete_Class_Name] => KPS\Logistics\Status\To\Canceled
[to] => canceled
[cancel_reason] => refund
[from] => canceled
[date] => DateTime Object
(
[date] => 2021-11-26 08:06:38.796815
[timezone_type] => 3
[timezone] => UTC
)
)
)
This is my model:
public class OrderItemStatusLogWpModel : IPhpObject
{
private string? _className;
[PhpProperty("cancel_reason")]
public string? CancelReason { get; set; }
[PhpProperty("delay_reason")]
public string? DelayReason { get; set; }
[PhpProperty("delay_until")]
public PhpDateTime? DelayUntil { get; set; }
[PhpProperty("from")]
public string? From { get; set; }
[PhpProperty("to")]
public string? To { get; set; }
[PhpProperty("date")]
public PhpDateTime? Date { get; set; }
#region IPhpObject
public string GetClassName() => _className ?? throw new InvalidOperationException($"{nameof(_className)} was not set yet, call {nameof(SetClassName)} first!");
public void SetClassName(string className) =>
_className = className;
#endregion
}
Now i remove the CancelReason
property, and run the same code:
public class OrderItemStatusLogWpModel : IPhpObject
{
private string? _className;
//[PhpProperty("cancel_reason")]
//public string? CancelReason { get; set; }
[PhpProperty("delay_reason")]
public string? DelayReason { get; set; }
[PhpProperty("delay_until")]
public PhpDateTime? DelayUntil { get; set; }
[PhpProperty("from")]
public string? From { get; set; }
[PhpProperty("to")]
public string? To { get; set; }
[PhpProperty("date")]
public PhpDateTime? Date { get; set; }
#region IPhpObject
public string GetClassName() => _className ?? throw new InvalidOperationException($"{nameof(_className)} was not set yet, call {nameof(SetClassName)} first!");
public void SetClassName(string className) =>
_className = className;
#endregion
}
And now suddenly the Date
property is not being set:
It seems to only be the last entry that is having difficulties:
Running v0.10.0
Failing tests:
[TestMethod]
public void Explicit_DeserializesInfinity() {
Assert.AreEqual(
double.PositiveInfinity,
PhpSerialization.Deserialize<double>("d:INF;")
);
}
[TestMethod]
public void Explicit_DeserializesNegativeInfinity() {
Assert.AreEqual(
double.NegativeInfinity,
PhpSerialization.Deserialize<double>("d:-INF;")
);
}
[TestMethod]
public void Explicit_Nullable_DeserializesInfinity() {
Assert.AreEqual(
double.PositiveInfinity,
PhpSerialization.Deserialize<double?>("d:INF;")
);
}
[TestMethod]
public void Explicit_Nullable_DeserializesNegativeInfinity() {
Assert.AreEqual(
double.NegativeInfinity,
PhpSerialization.Deserialize<double?>("d:-INF;")
);
}
float
(aka Single) has the same problem.
Hiya,
I just ran into the following issue and i was wondering if you could help me:
Input string:
a:1:{i:0;O:31:"KPS\Logistics\Status\To\Regular":3:{s:4:"from";s:8:"produced";s:2:"to";s:8:"produced";s:4:"date";O:8:"DateTime":3:{s:4:"date";s:26:"2021-08-18 09:10:23.441055";s:13:"timezone_type";i:3;s:8:"timezone";s:3:"UTC";}}}
Which gave me the following exception:
PhpSerializerNET.DeserializationException: 'Unexpected token 'O' at position 9.'
var statusLogString = items.First().statusLogString;
var deserialized = PhpSerializer.Deserialize(statusLogString, new PhpDeserializationOptions()
{
CaseSensitiveProperties = false
});
Also something i'm missing, when i have an object (and im using 'EnableTypeLookup' == false here ) it gets serialized into a dictionary - this is fine, however in that case i lose the classname - which i need to make sense of the object.
Would it be possible to add a key '__PHP_OBJECT_CLASSNAME' with the classname for an object?
Or perhaps you could do something like this:
class PhpSerializedObjectDictionary : Dictionary<string, object>
{
public string PhpObjectName { get; set; }
}
Sample:
a:1:{i:0;s:36:"82e2ebf0-43e6-4c10-82cf-57d60383a6be";}
It would be nice if i could translate the LabelId property here to a guid directly. Right now it throws unfortunately:
As a sidenote, i can do new GuidConverter().ConvertFromInvariantString(null, "82e2ebf0-43e6-4c10-82cf-57d60383a6be");
and it works fine.
Apperently Guid does not implement IConvertible.
https://stackoverflow.com/a/13066991/4122889
I'm also thinking general TypeConverter support would be cool so you can write custom converters - but idk about performance and so on for that one. Seems like supporting a Guid string is a broad enough use-case which would fit nicely in this library tho.
Done:
ToDo:
System.Dynamic.DynamicObject
and other implementations of System.Dynamic.IDynamicMetaObjectProvider
Perhaps a cool feature would be to allow for Php datetime objects to be directly translated to native System.DateTime.
I don't need this myself but it should be easy enough to implement - i have this code-snippet that does the job halfway already which we can use - so just posting this as a nice to have backlog item ^^.
private static DateTime? ParsePhpDateTimeStringSafe(string dateString, string timezone, long timezoneType)
{
try
{
return ParsePhpDateTimeString(dateString, timezone, timezoneType);
}
catch
{
// We're swallowing exceptions because we'll be handling null values instead.
return null;
}
}
private static DateTime ParsePhpDateTimeString(string dateString, string timezone, long timezoneType)
{
var localDateTimePattern = LocalDateTimePattern.CreateWithInvariantCulture("yyyy-MM-dd HH:mm:ss.ffffff");
var localDateTime = localDateTimePattern.Parse(dateString).GetValueOrThrow();
// See: https://stackoverflow.com/a/17711005/4122889
// Type 1; A UTC offset, such as in new DateTime("17 July 2013 -0300");
// Type 2; A timezone abbreviation, such as in new DateTime("17 July 2013 GMT");
// Type 3: A timezone identifier, such as in new DateTime("17 July 2013", new DateTimeZone("Europe/London"));
switch (timezoneType)
{
case 1:
var offSetPattern = OffsetPattern.CreateWithInvariantCulture("+HH:mm");
var offset = offSetPattern.Parse(timezone).Value;
var zonedDateTimeFromOffset = localDateTime.InZoneStrictly(DateTimeZone.ForOffset(offset));
return zonedDateTimeFromOffset.ToDateTimeUtc();
case 2:
throw new NotSupportedException("Not (Yet) support converting from timeZonetype 2 - but doable to add in!");
case 3:
var dateTimeZone = DateTimeZoneProviders.Tzdb[timezone];
var zonedDateTime = dateTimeZone.AtStrictly(localDateTime);
var dateTimeUtc = zonedDateTime.ToDateTimeUtc();
return dateTimeUtc;
default:
throw new ArgumentOutOfRangeException(nameof(timezoneType));
}
}
This does however use the NodaTime package which may not be a dependency we'd like. Optionally we could move this to a seperate package to give consumers more control over this.
We may also be able to get the code working without NodaTime, perhaps with something like https://github.com/mattjohnsonpint/TimeZoneNames for the timezonedb values but i have not given this much thought.
Hiya,
I just ran into an issue where i tried to assign an array of strings to a string. This caused a weird error in MakeObject() because it was trying to construct a string.
a:1:{i:0;s:1:"a";}
To string
.
Anyhow i spent some time what the issue was (from a lib consumer pov) and then found out the target type was just wrong. I feel like we can improve the validation a bit in this regard, where if i get a php array string and try to assign it to a string or int or w/e i get a specialized exception rather than something vague. (in this case it tried to construct the string in .MakeObject() which it can't.
Hiya,
It seems like the class name is not being set:
var phpString = "a:4:{i:0;O:31:\"KPS\\Logistics\\Status\\To\\Regular\":3:{s:4:\"from\";s:10:\"processing\";s:2:\"to\";s:8:\"produced\";s:4:\"date\";O:8:\"DateTime\":3:{s:4:\"date\";s:26:\"2018-11-05 06:03:27.000000\";s:13:\"timezone_type\";i:3;s:8:\"timezone\";s:3:\"UTC\";}}i:1;O:31:\"KPS\\Logistics\\Status\\To\\Delayed\":5:{s:4:\"from\";s:8:\"produced\";s:2:\"to\";s:7:\"delayed\";s:4:\"date\";O:8:\"DateTime\":3:{s:4:\"date\";s:26:\"2018-11-05 07:10:22.000000\";s:13:\"timezone_type\";i:3;s:8:\"timezone\";s:3:\"UTC\";}s:11:\"delay_until\";O:8:\"DateTime\":3:{s:4:\"date\";s:26:\"2018-11-06 00:00:00.000000\";s:13:\"timezone_type\";i:1;s:8:\"timezone\";s:6:\"+00:00\";}s:12:\"delay_reason\";s:12:\"out_of_stock\";}i:2;O:31:\"KPS\\Logistics\\Status\\To\\Regular\":3:{s:4:\"from\";s:7:\"delayed\";s:2:\"to\";s:8:\"produced\";s:4:\"date\";O:8:\"DateTime\":3:{s:4:\"date\";s:26:\"2018-11-06 09:26:30.000000\";s:13:\"timezone_type\";i:3;s:8:\"timezone\";s:3:\"UTC\";}}i:3;O:31:\"KPS\\Logistics\\Status\\To\\Regular\":3:{s:4:\"from\";s:8:\"produced\";s:2:\"to\";s:6:\"picked\";s:4:\"date\";O:8:\"DateTime\":3:{s:4:\"date\";s:26:\"2018-11-06 14:13:52.000000\";s:13:\"timezone_type\";i:3;s:8:\"timezone\";s:3:\"UTC\";}}}";
var phpString2 = "a:3:{i:0;O:31:\"KPS\\Logistics\\Status\\To\\Delayed\":5:{s:12:\"delay_reason\";s:12:\"out_of_stock\";s:11:\"delay_until\";O:8:\"DateTime\":3:{s:4:\"date\";s:26:\"2019-06-18 00:00:00.000000\";s:13:\"timezone_type\";i:1;s:8:\"timezone\";s:6:\"+00:00\";}s:4:\"from\";s:10:\"processing\";s:2:\"to\";s:7:\"delayed\";s:4:\"date\";O:8:\"DateTime\":3:{s:4:\"date\";s:26:\"2019-06-12 11:44:26.124104\";s:13:\"timezone_type\";i:3;s:8:\"timezone\";s:3:\"UTC\";}}i:1;O:31:\"KPS\\Logistics\\Status\\To\\Regular\":3:{s:4:\"from\";s:7:\"delayed\";s:2:\"to\";s:10:\"processing\";s:4:\"date\";O:8:\"DateTime\":3:{s:4:\"date\";s:26:\"2019-06-18 04:54:41.453555\";s:13:\"timezone_type\";i:3;s:8:\"timezone\";s:3:\"UTC\";}}i:2;O:31:\"KPS\\Logistics\\Status\\To\\Regular\":3:{s:4:\"from\";s:10:\"processing\";s:2:\"to\";s:8:\"produced\";s:4:\"date\";O:8:\"DateTime\":3:{s:4:\"date\";s:26:\"2019-06-18 09:50:40.847519\";s:13:\"timezone_type\";i:3;s:8:\"timezone\";s:3:\"UTC\";}}}";
var deserialize = PhpSerialization.Deserialize(phpString2);
var orderItemStatusLogWpModels = PhpSerialization.Deserialize<List<OrderItemStatusLogWpModel>>(phpString, new PhpDeserializationOptions()
{
AllowExcessKeys = true,
});
var orderItemStatusLogWpModels2 = PhpSerialization.Deserialize<List<OrderItemStatusLogWpModel>>(phpString2, new PhpDeserializationOptions()
{
AllowExcessKeys = true
});
public class OrderItemStatusLogWpModel : IPhpObject
{
private string? _className;
[PhpProperty("delay_reason")]
public string? DelayReason { get; set; }
[PhpProperty("delay_until")]
public PhpDateTime? DelayUntil { get; set; }
[PhpProperty("from")]
public string? From { get; set; }
[PhpProperty("to")]
public string? To { get; set; }
[PhpProperty("date")]
public PhpDateTime? Date { get; set; }
#region IPhpObject
public string GetClassName() => _className!;
public void SetClassName(string className) =>
_className = className;
#endregion
}
Also is there constructor injection support? (haven't looked) because that would allow for my models to not have to be nullable everywhere (in a nullable enabled context).
There's a couple scenarios where the current errorhandling doesn't catch the exceptions thrown.
See unit tests:
I'm writing an application that needs to be compatible with a legacy client whose code I can't change.
I have an issue where I have an object that has a nested array. The client expects the array to be serialized without a string key. It's just indexed starting with i:0
.
E.g.
My class:
public class MyClass
{
// How to avoid the `objects` key prefix?
public List<Objects>? objects { get; set; }
[PhpProperty("some_string")]
public string? some_string{ get; set; }
[PhpProperty("some_string2")]
public string? some_string2{ get; set; }
[PhpProperty("some_string3")]
public string? some_string3{ get; set; }
[PhpProperty("some_string4")]
public string? some_string4{ get; set; }
}
another option I tried was just using an object but not a list:
// How to have this be an integer index?
public Objects? objects { get; set; }
The output I'm hoping to match would look like this:
// Notice the array index `0` is used without any string key
a:5:{i:0;a:2:
What I'm actually getting [List version]:
a:5:{s:7:"objects";a:1:{i:0;a:2:
What I'm getting [without List version]:
a:5:{s:7:"objects";a:2:
Thanks in advance.
Unit tests as examples in the meantime:
https://github.com/StringEpsilon/PhpSerializerNET/blob/main/PhpSerializerNET.Test/
TODOs:
Hiya,
I'm trying to work with the following string:
a:1:{i:0;a:11:{s:14:"content_length";s:2:"96";s:13:"content_width";s:4:"14.5";s:14:"content_height";s:3:"1.5";s:14:"content_weight";s:5:"2.094";s:9:"belt_size";s:3:"146";s:5:"items";a:3:{i:108192;s:1:"1";i:108191;s:1:"1";i:108190;s:1:"1";}s:6:"length";s:3:"104";s:5:"width";s:2:"17";s:6:"height";s:1:"4";s:6:"weight";s:5:"2.362";s:9:"packaging";a:3:{s:2:"id";s:5:"84565";s:4:"cost";s:1:"0";s:6:"weight";s:5:"0.268";}}}
Which should parse fine:
Array
(
[0] => Array
(
[content_length] => 96
[content_width] => 14.5
[content_height] => 1.5
[content_weight] => 2.094
[belt_size] => 146
[items] => Array
(
[108192] => 1
[108191] => 1
[108190] => 1
)
[length] => 104
[width] => 17
[height] => 4
[weight] => 2.362
[packaging] => Array
(
[id] => 84565
[cost] => 0
[weight] => 0.268
)
)
)
https://www.unserialize.com/s/7bab51d5-6a0c-a748-bb0f-00004aac1852
Using the library - this works when UseLists
is the default value, but when i set it to Listoption.Never
, because 'items' is a dictionary and not a list (this is weird but out of my control..) it simply returns null;
var parcelAllocations = PhpSerialization.Deserialize(parcelAllocationMetaValue, new PhpDeserializationOptions
{
// Needed because of a b.u.g in the library TODO 27092021 typelookup issue seems to be fixed, classnames are also added so we can refactor the parsing to be somewhat nicer.
EnableTypeLookup = false,
// Needed because items is dictionary of integers and will be parsed as a list instead of a dict.
UseLists = ListOptions.Never
}) as List<object>;
Hiya,
When deserializing to an enum right now its mandatory to have the fields match the string exactly (AFAIK).
This however stops me from adhering to proper C# guidelines concerning naming and so on.
For example:
public enum OrderItemCouponType
{
Bol = 0,
// ReSharper disable InconsistentNaming
percent = 1,
fixed_product = 2,
fixed_cart = 3,
percent_product = 4,
// ReSharper restore InconsistentNaming
Unknown = 5,
Eol = 6
}
Should really be:
public enum OrderItemCouponType
{
Bol = 0,
Percent = 1,
FixedProduct = 2,
FixedCart = 3,
PercentProduct = 4,
Unknown = 5,
Eol = 6
}
I'd imagine reusing the PhpProperty
for this like
public enum OrderItemCouponType
{
Bol = 0,
[PhpProperty("percent")]
Percent = 1,
[PhpProperty("fixes_product")]
FixedProduct = 2,
[PhpProperty("fixed_cart")]
FixedCart = 3,
[PhpProperty("percent_product")]
PercentProduct = 4,
Unknown = 5,
Eol = 6
}
Let me know what you think.
Sample:
a:1:{i:0;a:7:{s:7:"labelId";s:36:"639a8673-c117-4dff-8e19-7c20bd8a98c1";s:11:"trackerCode";s:24:"JVGL06202840001797672669";s:10:"parcelType";s:5:"SMALL";s:11:"pieceNumber";i:1;s:6:"weight";d:1.0509999999999999;s:9:"labelType";s:20:"B2X_Generic_A4_Third";s:10:"dimensions";a:3:{s:6:"length";i:8;s:5:"width";d:2.5;s:6:"height";d:2.5;}}}
Array
(
[0] => Array
(
[labelId] => 639a8673-c117-4dff-8e19-7c20bd8a98c1
[trackerCode] => JVGL06202840001797672669
[parcelType] => SMALL
[pieceNumber] => 1
[weight] => 1.051
[labelType] => B2X_Generic_A4_Third
[dimensions] => Array
(
[length] => 8
[width] => 2.5
[height] => 2.5
)
)
)
This string is throwing exceptions, i tried using a DTO to deserialize into and without any options.
var s = "a:1:{i:0;a:7:{s:7:\"labelId\";s:36:\"639a8673-c117-4dff-8e19-7c20bd8a98c1\";s:11:\"trackerCode\";s:24:\"JVGL06202840001797672669\";s:10:\"parcelType\";s:5:\"SMALL\";s:11:\"pieceNumber\";i:1;s:6:\"weight\";d:1.0509999999999999;s:9:\"labelType\";s:20:\"B2X_Generic_A4_Third\";s:10:\"dimensions\";a:3:{s:6:\"length\";i:8;s:5:\"width\";d:2.5;s:6:\"height\";d:2.5;}}}";
var des = PhpSerialization.Deserialize(s);
public class Program
{
public static PhpDeserializationOptions PhpDeserializationOptions { get; set; } = new()
{// Needed for ParcelAllocation, which may or may not have 'supports'.
AllowExcessKeys = true
};
public static void Main()
{
var s = "a:1:{i:0;a:7:{s:7:\"labelId\";s:36:\"639a8673-c117-4dff-8e19-7c20bd8a98c1\";s:11:\"trackerCode\";s:24:\"JVGL06202840001797672669\";s:10:\"parcelType\";s:5:\"SMALL\";s:11:\"pieceNumber\";i:1;s:6:\"weight\";d:1.0509999999999999;s:9:\"labelType\";s:20:\"B2X_Generic_A4_Third\";s:10:\"dimensions\";a:3:{s:6:\"length\";i:8;s:5:\"width\";d:2.5;s:6:\"height\";d:2.5;}}}";
var des = PhpSerialization.Deserialize<KpsShippingExtraMetaPhpModel>(s, PhpDeserializationOptions);
Console.WriteLine(des);
}
}
public class KpsShippingExtraMetaPhpModel
{
[PhpProperty("labelId")]
public string LabelId { get; set; } // TODO PHP lib guid support ?
[PhpProperty("trackerCode")]
public string TrackerCode { get; set; }
[PhpProperty("parcelType")]
public int ParcelType { get; set; }
[PhpProperty("pieceNumber")]
public string PieceNumber { get; set; }
[PhpProperty("weight")]
public double Weight { get; set; }
[PhpProperty("labelType")]
public string LabelType { get; set; }
[PhpProperty("dimensions")]
public KpsShippingExtraMetaDimensionsPhpModel Dimensions { get; set; }
}
public class KpsShippingExtraMetaDimensionsPhpModel
{
[PhpProperty("length")]
public double Length { get; set; }
[PhpProperty("width")]
public double Width { get; set; }
[PhpProperty("height")]
public double Height { get; set; }
}
Apperently the validate format faults.
.Net core 5 with v7.0.0
I've got a case where sometimes a property is present in my php string and sometimes it is not.
Take these 2 strings as samples:
With supports
data
a:1:{i:0;a:12:{s:14:"content_length";s:5:"112.5";s:13:"content_width";s:2:"28";s:14:"content_height";s:1:"5";s:14:"content_weight";s:5:"7.222";s:9:"belt_size";s:5:"206.5";s:5:"items";a:1:{i:222897;s:1:"5";}s:6:"length";s:5:"120.5";s:5:"width";s:2:"33";s:6:"height";s:2:"10";s:6:"weight";s:5:"8.167";s:9:"packaging";a:3:{s:2:"id";s:6:"150514";s:4:"cost";s:1:"0";s:6:"weight";s:5:"0.945";}s:8:"supports";a:5:{s:8:"material";s:9:"honeycomb";s:6:"length";s:5:"112.5";s:5:"width";s:2:"28";s:6:"height";s:1:"3";s:6:"weight";s:5:"0.337";}}}
Array
(
[0] => Array
(
[content_length] => 112.5
[content_width] => 28
[content_height] => 5
[content_weight] => 7.222
[belt_size] => 206.5
[items] => Array
(
[222897] => 5
)
[length] => 120.5
[width] => 33
[height] => 10
[weight] => 8.167
[packaging] => Array
(
[id] => 150514
[cost] => 0
[weight] => 0.945
)
[supports] => Array
(
[material] => honeycomb
[length] => 112.5
[width] => 28
[height] => 3
[weight] => 0.337
)
)
)
And without supports
a:1:{i:0;a:11:{s:14:"content_length";s:2:"96";s:13:"content_width";s:4:"14.5";s:14:"content_height";s:3:"1.5";s:14:"content_weight";s:5:"2.094";s:9:"belt_size";s:3:"146";s:5:"items";a:3:{i:108192;s:1:"1";i:108191;s:1:"1";i:108190;s:1:"1";}s:6:"length";s:3:"104";s:5:"width";s:2:"17";s:6:"height";s:1:"4";s:6:"weight";s:5:"2.362";s:9:"packaging";a:3:{s:2:"id";s:5:"84565";s:4:"cost";s:1:"0";s:6:"weight";s:5:"0.268";}}}
Array
(
[0] => Array
(
[content_length] => 96
[content_width] => 14.5
[content_height] => 1.5
[content_weight] => 2.094
[belt_size] => 146
[items] => Array
(
[108192] => 1
[108191] => 1
[108190] => 1
)
[length] => 104
[width] => 17
[height] => 4
[weight] => 2.362
[packaging] => Array
(
[id] => 84565
[cost] => 0
[weight] => 0.268
)
)
)
Right now i'm mapping these objects like so:
public class ParcelAllocationWithSupportPhpModel : ParcelAllocationPhpModel
{
public ParcelAllocationSupportsPhpModel supports { get; set; }
public static bool TryDeserializeFromList(string input, out List<ParcelAllocationWithSupportPhpModel> parcelAllocationPhpModels)
{
try
{
parcelAllocationPhpModels = DeserializeFromList(input);
return true;
}
catch (Exception)
{
parcelAllocationPhpModels = null;
return false;
}
}
public static List<ParcelAllocationWithSupportPhpModel> DeserializeFromList(string input) =>
PhpSerialization.Deserialize<List<ParcelAllocationWithSupportPhpModel>>(input);
public static bool TryDeserialize(string input, out ParcelAllocationWithSupportPhpModel parcelAllocationPhpModels)
{
try
{
parcelAllocationPhpModels = Deserialize(input);
return true;
}
catch (Exception)
{
parcelAllocationPhpModels = null;
return false;
}
}
public static ParcelAllocationWithSupportPhpModel Deserialize(string input) =>
PhpSerialization.Deserialize<ParcelAllocationWithSupportPhpModel>(input);
public static string Serialize(ParcelAllocationWithSupportPhpModel model) =>
PhpSerialization.Serialize(model);
}
public class ParcelAllocationPhpModel
{
public double content_length { get; set; }
public double content_width { get; set; }
public double content_height { get; set; }
public double content_weight { get; set; }
public double belt_size { get; set; }
public Dictionary<long, int> items { get; set; } // TODO sometimes ints, sometimes strings (guids from marketplace) - Change to Dict<string,int> so that we can parse them both!
public double length { get; set; }
public double width { get; set; }
public double height { get; set; }
public double weight { get; set; }
public ParcelAllocationPackagingPhpModel packaging { get; set; }
public static bool TryDeserializeFromList(string input, out List<ParcelAllocationPhpModel> parcelAllocationPhpModels)
{
try
{
parcelAllocationPhpModels = DeserializeFromList(input);
return true;
}
catch (Exception)
{
parcelAllocationPhpModels = null;
return false;
}
}
public static List<ParcelAllocationPhpModel> DeserializeFromList(string input) =>
PhpSerialization.Deserialize<List<ParcelAllocationPhpModel>>(input);
public static bool TryDeserialize(string input, out ParcelAllocationPhpModel parcelAllocationPhpModels)
{
try
{
parcelAllocationPhpModels = Deserialize(input);
return true;
}
catch (Exception)
{
parcelAllocationPhpModels = null;
return false;
}
}
public static ParcelAllocationPhpModel Deserialize(string input) =>
PhpSerialization.Deserialize<ParcelAllocationPhpModel>(input);
public static string Serialize(ParcelAllocationPackagingPhpModel model) =>
PhpSerialization.Serialize(model);
}
List<ParcelAllocationPhpModel> parcelAllocationPhpModels = null;
if (!ParcelAllocationPhpModel.TryDeserializeFromList(parcelAllocationMetaValue, out parcelAllocationPhpModels))
{
if (ParcelAllocationWithSupportPhpModel.TryDeserializeFromList(parcelAllocationMetaValue, out var parcelAllocationWithSupportPhpModels))
{
parcelAllocationPhpModels = parcelAllocationWithSupportPhpModels.Cast<ParcelAllocationPhpModel>().ToList();
}
}
This is a) a mess and b) has the sideeffect that i no longer can read the supports
property.
What i'd like is to be able to have any properties that are not present in the PHP string to be set to their default value. In my case supports
would be null - or have a ParcelAllocationSupportsPhpModel
if that was present. I think this would also mimick the behvaiour of newtonsoft Json.Net.
Anyhow i'd imagine my code to become something like:
var parcelAllocationPhpModels = ParcelAllocationPhpModel.DeserializeFromList(input);
var firstSupports = parcelAllocationPhpModels[0].supports; // default (null) or an object
Depending on how the serializer works we can also work with [required]
and [optional]
attributes. There are some in the std lib but perhaps to not attach dependencies we can roll our own.
@StringEpsilon i can imagine you're busy so any guidance on self implementing this would be cool.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.