Skip to content

Instantly share code, notes, and snippets.

@xiangwan
Created September 19, 2011 04:50
Show Gist options
  • Save xiangwan/1225981 to your computer and use it in GitHub Desktop.
Save xiangwan/1225981 to your computer and use it in GitHub Desktop.
C# php 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
/// <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
@andersoonluan
Copy link

Thanks! Great work!!

@ItsClemi
Copy link

Thanks 💯 :)

@hendrasyp
Copy link

Awesome.
Can you tell me how to convert this to List ?

@laloutre87
Copy link

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.

@laloutre87
Copy link

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 .

@tasgray
Copy link

tasgray commented Jan 30, 2021

Working perfectly, thank you!

@sipi41
Copy link

sipi41 commented Apr 26, 2021

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!

@laloutre87
Copy link

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 ?

@sipi41
Copy link

sipi41 commented Apr 26, 2021

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!

@sipi41
Copy link

sipi41 commented Apr 28, 2021

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:

  1. I installed the PeachPie VS extension
  2. After VS restart, I did open my solution again and added a new project (to my solution) of type "Peachpie Class Library"
  3. 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;
	
}

}`

  1. Now I go back to my original project, right-click Dependencies, and clicked "Add Project Reference", choosing the project where my PHP function is.
  2. 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!

@StringEpsilon
Copy link

StringEpsilon commented Sep 15, 2021

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment