php语言中,有自己封装好的序列化跟反序列化的方法,如果我们在java项目中需要反序列化由php序列化的数据,我们可以使用PHPSerializer工具类
pom文件中引入依赖
<!-- 反序列化php序列化过的字符串 -->
<dependency>
<groupId>org.sction</groupId>
<artifactId>phprpc</artifactId>
<version>3.0.2</version>
</dependency>
使用也非常简单,由于序列化的数据大多数是json,该工具会反序列化成AssocArray类对象,所以写法如下:
public static AssocArray unSerializePhpStr(String str) throws InvocationTargetException, IllegalAccessException {
if(StringUtils.isEmpty(str)) {
return null;
}
PHPSerializer p = new PHPSerializer();
return (AssocArray) p.unserialize(str.getBytes());
}
我们反序列化一个简单的json字符串:a:1:{i:123;s:2:"id"}
String str = "a:1:{i:123;s:2:\"id\"}";
AssocArray object = PhpSerializerUtil.unSerializePhpStr(str);
HashMap hashMap = object.toHashMap();
反序列化成功了,我们可以使用toHashMap()将AssocArray对象转成HashMap对象
但是这只是简单的json反序列化,php在序列化的时候,数字类型都会序列化成i类型,而在java中会区分int和long类型,所以如果数值太大,在java中会反序列化失败,比如:
a:2:{i:1;i:1649729539760;s:2:"id";i:2;}
我们反序列化一下试试
发现直接报错,我们去看一下源码:
这里可以看到,i类型直接会映射成Integer,所以长度会有限制。我们再仔细看,发现有个d类型可以映射成Double,那我们将i全部改成d试试
str.replaceAll("i:","d:");
再试一下
发现返回的直接是null,虽然没报错,但是明显不对,我们再看看源码:
i跟d的映射应该没有问题,问题出现在a上面,我们进去看看
json是<k,v>结构,可以看到AssoArray对象的key并不支持Double类型,所以直接返回了null,所以只能将value类型由i转成d,key不能改,但是反序列化之后的字符串特别难区分key和value,写起来很麻烦,所以干脆,直接将i类型转成s(String),这样key和value都支持
public static AssocArray unSerializePhpStr(String str) throws InvocationTargetException, IllegalAccessException {
if(StringUtils.isEmpty(str)) {
return null;
}
//整形全部转成字符串,避免长度超出(key不支持其他数字类型)
String numStr = "i:";
while (str.contains(numStr)) {
int index = str.indexOf(numStr);
String str1 = str.substring(0,index);
String str2 = str.substring(index);
int index1 = str2.indexOf(";");
str = String.format("%ss:%d:\"%s\"%s",str1,index1-2,str2.substring(2,index1),str2.substring(index1));
}
PHPSerializer p = new PHPSerializer();
return (AssocArray) p.unserialize(str.getBytes());
}
我们再试试:
发现成功了
注意:AssocArray对象中的字符串类型value全都是byte数组,需要自己手动转成String
补充:AssocArray对象处理起来太麻烦,补充一个返回json的方法
/**
* 将所有的AssocArray转成java
* @param str
* @return
* @throws InvocationTargetException
* @throws IllegalAccessException
*/
public static JSONObject unSerializePhpStrBackJson(String str) throws InvocationTargetException, IllegalAccessException {
AssocArray assocArray = unSerializePhpStr(str);
return assocArrayToHash(assocArray);
}
private static JSONObject assocArrayToHash(AssocArray assocArray) {
HashMap hashMap = assocArray.toHashMap();
JSONObject result = new JSONObject();
for (Object key : hashMap.keySet()) {
if(hashMap.get(key) instanceof AssocArray) {
result.put(key.toString(),assocArrayToHash((AssocArray) hashMap.get(key)));
}else if(hashMap.get(key) instanceof byte[]) {
result.put(key.toString(),new String((byte[]) hashMap.get(key)));
}else {
result.put(key.toString(),hashMap.get(key));
}
}
return result;
}
***********************补充2022/11/18
1、如果序列化后的数据包含转义字符,这个时候会反序列化失败,我们需要去除转义字符
str = str.replaceAll("\\\\\"","\"");
2、如果php序列化的数据包含中文,php可能会将其序列化为16进制编码,比如下面的数据
a:1:{s:4:"name";s:6:"\xe5\x90\x8d\xe7\x89\x87";}
这个时候同样会反序列化失败,此时,我们需要将16进制编码转变成中文
public static String hex2UTF8(String hexStr) throws UnsupportedEncodingException {
return URLDecoder.decode(hexStr.replaceAll("\\\\x", "%"), StandardCharsets.UTF_8.name());
}
先调用这个方法去除一下16进制编码,然后再反序列化就可以了