-
-
Save xiangwan/1225981 to your computer and use it in GitHub Desktop.
/// <summary> | |
/// Serializer Class. | |
/// </summary> | |
public class Serializer | |
{ | |
//types: | |
// N = null | |
// s = string | |
// i = int | |
// d = double | |
// a = array (hashtable) | |
private Dictionary<Hashtable, bool> seenHashtables; //for serialize (to infinte prevent loops) | |
private Dictionary<ArrayList, bool> seenArrayLists; //for serialize (to infinte prevent loops) lol | |
private int pos; //for unserialize | |
public bool XMLSafe = true; //This member tells the serializer wether or not to strip carriage returns from strings when serializing and adding them back in when deserializing | |
//http://www.w3.org/TR/REC-xml/#sec-line-ends | |
public Encoding StringEncoding = new System.Text.UTF8Encoding(); | |
private System.Globalization.NumberFormatInfo nfi; | |
public Serializer() | |
{ | |
this.nfi = new System.Globalization.NumberFormatInfo(); | |
this.nfi.NumberGroupSeparator = ""; | |
this.nfi.NumberDecimalSeparator = "."; | |
} | |
public string Serialize(object obj) | |
{ | |
this.seenArrayLists = new Dictionary<ArrayList, bool>(); | |
this.seenHashtables = new Dictionary<Hashtable, bool>(); | |
return this.serialize(obj, new StringBuilder()).ToString(); | |
}//Serialize(object obj) | |
private StringBuilder serialize(object obj, StringBuilder sb) | |
{ | |
if (obj == null) | |
{ | |
return sb.Append("N;"); | |
} | |
else if (obj is string) | |
{ | |
string str = (string)obj; | |
if (this.XMLSafe) | |
{ | |
str = str.Replace("\r\n", "\n");//replace \r\n with \n | |
str = str.Replace("\r", "\n");//replace \r not followed by \n with a single \n Should we do this? | |
} | |
return sb.Append("s:" + this.StringEncoding.GetByteCount(str) + ":\"" + str + "\";"); | |
} | |
else if (obj is bool) | |
{ | |
return sb.Append("b:" + (((bool)obj) ? "1" : "0") + ";"); | |
} | |
else if (obj is int) | |
{ | |
int i = (int)obj; | |
return sb.Append("i:" + i.ToString(this.nfi) + ";"); | |
} | |
else if (obj is double) | |
{ | |
double d = (double)obj; | |
return sb.Append("d:" + d.ToString(this.nfi) + ";"); | |
} | |
else if (obj is ArrayList) | |
{ | |
if (this.seenArrayLists.ContainsKey((ArrayList)obj)) | |
return sb.Append("N;");//cycle detected | |
else | |
this.seenArrayLists.Add((ArrayList)obj, true); | |
ArrayList a = (ArrayList)obj; | |
sb.Append("a:" + a.Count + ":{"); | |
for (int i = 0; i < a.Count; i++) | |
{ | |
this.serialize(i, sb); | |
this.serialize(a[i], sb); | |
} | |
sb.Append("}"); | |
return sb; | |
} | |
else if (obj is Hashtable) | |
{ | |
if (this.seenHashtables.ContainsKey((Hashtable)obj)) | |
return sb.Append("N;");//cycle detected | |
else | |
this.seenHashtables.Add((Hashtable)obj, true); | |
Hashtable a = (Hashtable)obj; | |
sb.Append("a:" + a.Count + ":{"); | |
foreach (DictionaryEntry entry in a) | |
{ | |
this.serialize(entry.Key, sb); | |
this.serialize(entry.Value, sb); | |
} | |
sb.Append("}"); | |
return sb; | |
} | |
else | |
{ | |
return sb; | |
} | |
}//Serialize(object obj) | |
public object Deserialize(string str) | |
{ | |
this.pos = 0; | |
return deserialize(str); | |
}//Deserialize(string str) | |
private object deserialize(string str) | |
{ | |
if (str == null || str.Length <= this.pos) | |
return new Object(); | |
int start, end, length; | |
string stLen; | |
switch (str[this.pos]) | |
{ | |
case 'N': | |
this.pos += 2; | |
return null; | |
case 'b': | |
char chBool; | |
chBool = str[pos + 2]; | |
this.pos += 4; | |
return chBool == '1'; | |
case 'i': | |
string stInt; | |
start = str.IndexOf(":", this.pos) + 1; | |
end = str.IndexOf(";", start); | |
stInt = str.Substring(start, end - start); | |
this.pos += 3 + stInt.Length; | |
return Int32.Parse(stInt, this.nfi); | |
case 'd': | |
string stDouble; | |
start = str.IndexOf(":", this.pos) + 1; | |
end = str.IndexOf(";", start); | |
stDouble = str.Substring(start, end - start); | |
this.pos += 3 + stDouble.Length; | |
return Double.Parse(stDouble, this.nfi); | |
case 's': | |
start = str.IndexOf(":", this.pos) + 1; | |
end = str.IndexOf(":", start); | |
stLen = str.Substring(start, end - start); | |
int bytelen = Int32.Parse(stLen); | |
length = bytelen; | |
//This is the byte length, not the character length - so we migth | |
//need to shorten it before usage. This also implies bounds checking | |
if ((end + 2 + length) >= str.Length) length = str.Length - 2 - end; | |
string stRet = str.Substring(end + 2, length); | |
while (this.StringEncoding.GetByteCount(stRet) > bytelen) | |
{ | |
length--; | |
stRet = str.Substring(end + 2, length); | |
} | |
this.pos += 6 + stLen.Length + length; | |
if (this.XMLSafe) | |
{ | |
stRet = stRet.Replace("\n", "\r\n"); | |
} | |
return stRet; | |
case 'a': | |
//if keys are ints 0 through N, returns an ArrayList, else returns Hashtable | |
start = str.IndexOf(":", this.pos) + 1; | |
end = str.IndexOf(":", start); | |
stLen = str.Substring(start, end - start); | |
length = Int32.Parse(stLen); | |
Hashtable htRet = new Hashtable(length); | |
ArrayList alRet = new ArrayList(length); | |
this.pos += 4 + stLen.Length; //a:Len:{ | |
for (int i = 0; i < length; i++) | |
{ | |
//read key | |
object key = deserialize(str); | |
//read value | |
object val = deserialize(str); | |
if (alRet != null) | |
{ | |
if (key is int && (int)key == alRet.Count) | |
alRet.Add(val); | |
else | |
alRet = null; | |
} | |
htRet[key] = val; | |
} | |
this.pos++; //skip the } | |
if (this.pos < str.Length && str[this.pos] == ';')//skipping our old extra array semi-colon bug (er... php's weirdness) | |
this.pos++; | |
if (alRet != null) | |
return alRet; | |
else | |
return htRet; | |
default: | |
return ""; | |
}//switch | |
}//unserialzie(object) | |
}//class Serializer |
/// <summary> | |
/// Serializer Class. | |
/// </summary> | |
public class Serializer | |
{ | |
//types: | |
// N = null | |
// s = string | |
// i = int | |
// d = double | |
// a = array (hashtable) | |
private Dictionary<Hashtable, bool> seenHashtables; //for serialize (to infinte prevent loops) | |
private Dictionary<ArrayList, bool> seenArrayLists; //for serialize (to infinte prevent loops) lol | |
private int pos; //for unserialize | |
public bool XMLSafe = true; //This member tells the serializer wether or not to strip carriage returns from strings when serializing and adding them back in when deserializing | |
//http://www.w3.org/TR/REC-xml/#sec-line-ends | |
public Encoding StringEncoding = new System.Text.UTF8Encoding(); | |
private System.Globalization.NumberFormatInfo nfi; | |
public Serializer() | |
{ | |
this.nfi = new System.Globalization.NumberFormatInfo(); | |
this.nfi.NumberGroupSeparator = ""; | |
this.nfi.NumberDecimalSeparator = "."; | |
} | |
public string Serialize(object obj) | |
{ | |
this.seenArrayLists = new Dictionary<ArrayList, bool>(); | |
this.seenHashtables = new Dictionary<Hashtable, bool>(); | |
return this.serialize(obj, new StringBuilder()).ToString(); | |
}//Serialize(object obj) | |
private StringBuilder serialize(object obj, StringBuilder sb) | |
{ | |
if (obj == null) | |
{ | |
return sb.Append("N;"); | |
} | |
else if (obj is string) | |
{ | |
string str = (string)obj; | |
if (this.XMLSafe) | |
{ | |
str = str.Replace("\r\n", "\n");//replace \r\n with \n | |
str = str.Replace("\r", "\n");//replace \r not followed by \n with a single \n Should we do this? | |
} | |
return sb.Append("s:" + this.StringEncoding.GetByteCount(str) + ":\"" + str + "\";"); | |
} | |
else if (obj is bool) | |
{ | |
return sb.Append("b:" + (((bool)obj) ? "1" : "0") + ";"); | |
} | |
else if (obj is int) | |
{ | |
int i = (int)obj; | |
return sb.Append("i:" + i.ToString(this.nfi) + ";"); | |
} | |
else if (obj is double) | |
{ | |
double d = (double)obj; | |
return sb.Append("d:" + d.ToString(this.nfi) + ";"); | |
} | |
else if (obj is ArrayList) | |
{ | |
if (this.seenArrayLists.ContainsKey((ArrayList)obj)) | |
return sb.Append("N;");//cycle detected | |
else | |
this.seenArrayLists.Add((ArrayList)obj, true); | |
ArrayList a = (ArrayList)obj; | |
sb.Append("a:" + a.Count + ":{"); | |
for (int i = 0; i < a.Count; i++) | |
{ | |
this.serialize(i, sb); | |
this.serialize(a[i], sb); | |
} | |
sb.Append("}"); | |
return sb; | |
} | |
else if (obj is Hashtable) | |
{ | |
if (this.seenHashtables.ContainsKey((Hashtable)obj)) | |
return sb.Append("N;");//cycle detected | |
else | |
this.seenHashtables.Add((Hashtable)obj, true); | |
Hashtable a = (Hashtable)obj; | |
sb.Append("a:" + a.Count + ":{"); | |
foreach (DictionaryEntry entry in a) | |
{ | |
this.serialize(entry.Key, sb); | |
this.serialize(entry.Value, sb); | |
} | |
sb.Append("}"); | |
return sb; | |
} | |
else | |
{ | |
return sb; | |
} | |
}//Serialize(object obj) | |
public object Deserialize(string str) | |
{ | |
this.pos = 0; | |
return deserialize(str); | |
}//Deserialize(string str) | |
private object deserialize(string str) | |
{ | |
if (str == null || str.Length <= this.pos) | |
return new Object(); | |
int start, end, length; | |
string stLen; | |
switch (str[this.pos]) | |
{ | |
case 'N': | |
this.pos += 2; | |
return null; | |
case 'b': | |
char chBool; | |
chBool = str[pos + 2]; | |
this.pos += 4; | |
return chBool == '1'; | |
case 'i': | |
string stInt; | |
start = str.IndexOf(":", this.pos) + 1; | |
end = str.IndexOf(";", start); | |
stInt = str.Substring(start, end - start); | |
this.pos += 3 + stInt.Length; | |
return Int32.Parse(stInt, this.nfi); | |
case 'd': | |
string stDouble; | |
start = str.IndexOf(":", this.pos) + 1; | |
end = str.IndexOf(";", start); | |
stDouble = str.Substring(start, end - start); | |
this.pos += 3 + stDouble.Length; | |
return Double.Parse(stDouble, this.nfi); | |
case 's': | |
start = str.IndexOf(":", this.pos) + 1; | |
end = str.IndexOf(":", start); | |
stLen = str.Substring(start, end - start); | |
int bytelen = Int32.Parse(stLen); | |
length = bytelen; | |
//This is the byte length, not the character length - so we migth | |
//need to shorten it before usage. This also implies bounds checking | |
if ((end + 2 + length) >= str.Length) length = str.Length - 2 - end; | |
string stRet = str.Substring(end + 2, length); | |
while (this.StringEncoding.GetByteCount(stRet) > bytelen) | |
{ | |
length--; | |
stRet = str.Substring(end + 2, length); | |
} | |
this.pos += 6 + stLen.Length + length; | |
if (this.XMLSafe) | |
{ | |
stRet = stRet.Replace("\n", "\r\n"); | |
} | |
return stRet; | |
case 'a': | |
//if keys are ints 0 through N, returns an ArrayList, else returns Hashtable | |
start = str.IndexOf(":", this.pos) + 1; | |
end = str.IndexOf(":", start); | |
stLen = str.Substring(start, end - start); | |
length = Int32.Parse(stLen); | |
Hashtable htRet = new Hashtable(length); | |
ArrayList alRet = new ArrayList(length); | |
this.pos += 4 + stLen.Length; //a:Len:{ | |
for (int i = 0; i < length; i++) | |
{ | |
//read key | |
object key = deserialize(str); | |
//read value | |
object val = deserialize(str); | |
if (alRet != null) | |
{ | |
if (key is int && (int)key == alRet.Count) | |
alRet.Add(val); | |
else | |
alRet = null; | |
} | |
htRet[key] = val; | |
} | |
this.pos++; //skip the } | |
if (this.pos < str.Length && str[this.pos] == ';')//skipping our old extra array semi-colon bug (er... php's weirdness) | |
this.pos++; | |
if (alRet != null) | |
return alRet; | |
else | |
return htRet; | |
default: | |
return ""; | |
}//switch | |
}//unserialzie(object) | |
}//class Serializer |
Anyone else having performance trouble on linux with .net core ? PHP's serialized array ( about 30k elements ) takes 2 seconds to unserialize on windows, but more than an hour on linux.
So, for anyone strugling on .netcore + linux, the .IndexOf(string , ... ) function is completely busted on linux, incredibly slow. So changing all the
str.IndexOf(":"
by
str.IndexOf(':'
will solve the speed issue ( and bonus, it's also twice faster on windows .
Working perfectly, thank you!
I would like help with something I consider an error... I have the following serialized PHP string on a MySQL db:
a:1:{i:0;O:8:"stdClass":5:{s:2:"id";s:11:"fee-5-money";s:4:"name";s:31:"Anuncio 4 semanas (Web) Houston";s:11:"description";s:25:"4 Weeks Ad (Web Only) HOU";s:12:"payment_type";s:5:"money";s:6:"amount";d:45;}}
I'm expecting to get something like this:
[{"id":"fee-2-money","name":"Anuncio 2 semanas (Web) Houston","description":"2 Weeks Ad (Web) all Houston Area","payment_type":"money","amount":25}]
Instead what I get is an empty array like this:
[""]
Is this a bug? I also tried "PHPSerializationLibrary" and the result is the same (this is why I left there and tried this one), please help, thank you!
a:1:{i:0;O:8:"stdClass":5:{s:2:"id";s:11:"fee-5-money";s:4:"name";s:31:"Anuncio 4 semanas (Web) Houston";s:11:"description";s:25:"4 Weeks Ad (Web Only) HOU";s:12:"payment_type";s:5:"money";s:6:"amount";d:45;}}
This code does not de-serialize objects. What would you like to de-serialize into ? a dynamic ? an anonymous object ? dictionary ?
When transmitting data between languages, i would say that keeping it as simple as possible would be better, you have no other way than having an object on the Php side ?
FIRST MESSAGE @laloutre87 thank you for your prompt answer... I'm not expecting the json as result, but I do understand this should return an object that I could serialize to JSON later on, am I right?
On the other hand, I was looking at the code response according to the first character of the part of the string is reading, so for an "a" it uses a case 'a', case 'i', and so on... now I see the problem starts with this part "O:8", (near the beginning of the string) there is nothing on the script that understand what to do when a letter O is found... it says what to do with a letter N but not an O, this is why the response is an empty object.
I would like to get any object type, but something that I could inspect and use what is inside... Thank you for your help.
SECOND MESSAGE What happen is that the code was not designed to accept Objects, just arrays, this is why it does not recognize the object. (https://en.wikipedia.org/wiki/PHP_serialization_format), I would like to ask @xiangwan if he could update or any of you could suggest a better way to convert PHP serialized objects to C#. Thank you all!
EUREKA! EUREKA!
The problem with this class is that it does de-serialize Php serialized Arrays, not Objects, this is why it cannot translate what an object contains.
I found the solution: The solution is to use the PHP native serialize() and unserialize() functions. To do this I did follow the steps:
- I installed the PeachPie VS extension
- After VS restart, I did open my solution again and added a new project (to my solution) of type "Peachpie Class Library"
- I created a public class with 2 public methods as follow: (but you can add extra PHP, as you can build anything PHP there)
`<?php
namespace PHPserializer;
class PHPhelper
{
public function FromPHPtoJson(string $object) : string
{
$result = unserialize($object);
$jsonres = json_encode($result);
return $jsonres;
}
}`
- Now I go back to my original project, right-click Dependencies, and clicked "Add Project Reference", choosing the project where my PHP function is.
- Then I can call the PHP function that returns the desired Json like this:
using PHPserializer; ... var PHPhelper = new PHPhelper(); string JsonString = PHPhelper.FromPHPtoJson(MY-PHP-SERIALIZED-STRING).ToString();
So, we do not depend on our own code, instead we call directly PHP to resolve the issue. Hope this helps!
FYI, I wrote my own PHP de/serialization library because I ran into some limitations of this one (both in terms of functionality and license).
https://github.com/StringEpsilon/PhpSerializerNET
Update: It has full support for the object notation now.
Awesome.
Can you tell me how to convert this to List ?