上一篇中,实现了json字符串的解析;但是一个json库真正cool的地方是跟实体bean的转换,于是今天便来撸下。这次其实跟编译方面没有任何关系,主要是java反射的运用。之前反射用的也少,正好一边百度一边练练。
基本功能码完后的感受就是,看似简单的一个实体映射,其实要考虑的细节非常之多,因此也只能实现个大概,离真正能在生产环境使用还差的远。
以下是一个测试bean
package com.ff.fun.testbean;
import java.math.BigDecimal;
/**
* Created by Administrator on 2018/5/22.
*/
public class Apple extends Fruit{
private String color;
private BigDecimal price;
private double radius;
private int sweetness;
private boolean mature;
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
public BigDecimal getPrice() {
return price;
}
public void setPrice(BigDecimal price) {
this.price = price;
}
public double getRadius() {
return radius;
}
public void setRadius(double radius) {
this.radius = radius;
}
public int getSweetness() {
return sweetness;
}
public void setSweetness(int sweetness) {
this.sweetness = sweetness;
}
public boolean isMature() {
return mature;
}
public void setMature(boolean mature) {
this.mature = mature;
}
}
package com.ff.fun.testbean;
import java.util.List;
/**
* Created by Administrator on 2018/5/22.
*/
public class Farmer {
private String name;
private int age;
private Address address;
private List<Apple> appleList;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Address getAddress() {
return address;
}
public void setAddress(Address address) {
this.address = address;
}
public List<Apple> getAppleList() {
return appleList;
}
public void setAppleList(List<Apple> appleList) {
this.appleList = appleList;
}
}
那么,首先,规定一个属性同时拥有getter和setter才会被映射,如getPrice,setPrice表明price这个属性是我们需要映射的属性。另外,对于boolean,其getter为isXXX,这也得特殊的考虑以下。
private Map<String,Method[]> buildPropertyMap(Class clz){
Map<String,Method[]> _propertyMap = new HashMap<>();
for(Method m : clz.getMethods()){
if(Modifier.isStatic(m.getModifiers())){
continue;
}
String mName = m.getName();
if(mName.startsWith("set") && mName.length() > 3){
putInPropertyMap(_propertyMap,1,m);
}else if(mName.startsWith("get") && mName.length() > 3 || mName.startsWith("is") && mName.length() > 2){
putInPropertyMap(_propertyMap,0,m);
}
}
Map<String,Method[]> propertyMap = new HashMap<>();
for(String pName : _propertyMap.keySet()){
Method[] ms = _propertyMap.get(pName);
if(ms[0] != null && ms[1] != null){
propertyMap.put(pName,_propertyMap.get(pName));
}
}
return propertyMap;
}
上述方法,建立两个一个map,key为属性名,值是个method数组,分别代表该属性的getter和setter方法。
有了这个,可以正式的开始映射属性了:
public <T> T toBean(FFJsonObject ffJsonObject, Class<T> clz){
T t;
try {
t = clz.newInstance();
} catch (Exception e) {
return null;
}
Map<String,Method[]> propertyMap = buildPropertyMap(clz);
for(String pName : propertyMap.keySet()) {
Method set = propertyMap.get(pName)[1];
Object value = ffJsonObject.get(pName);
Class expectedClz = set.getParameterTypes()[0];
if(value == null){
continue;
}
if(value instanceof FFJsonArray){
Type type = ((ParameterizedType)set.getParameters()[0].getParameterizedType()).getActualTypeArguments()[0];
try {
Class eleClz = Class.forName(type.getTypeName());
value = toBeanList((FFJsonArray)value,eleClz);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}else if(value instanceof FFJsonObject){
value = toBean((FFJsonObject)value,expectedClz);
}else{
//TODO make this a serializer pipeline
if(expectedClz == BigDecimal.class){
value = new BigDecimal(value.toString());
}else if(expectedClz == int.class || expectedClz == Integer.class){
value = Integer.parseInt(value.toString());
}else if(expectedClz == float.class || expectedClz == Float.class){
value = Float.parseFloat(value.toString());
}
}
try {
set.invoke(t,value);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
return t;
}
在TODO的地方,就是著名json库中的Serializer所发挥效用的地方了,最常见的便是Date,当然此处我没实现;
另外,bean Farmer中的appleList属性,是List<Apple>类型的,如何利用反射,获取其元素的类Apple,也是我自认为的一个难点。如以上源码可见,先获取其type,然后使用Class.forName,最终获取到了这个类Apple。
接下来便是愉快的使用了:
@Test
public void toBean(){
Apple apple = FFJson.toBean("{\"color\":\"red\",\"mature\":true,\"price\":4.2,\"sweetness\":7,\"weight\":2.0,\"radius\":5.0}",Apple.class);
System.out.println(apple.getColor());
assertEquals(true,apple.isMature());
List<Address> addressList = FFJson.toBeanList("[{\"city\":\"邵阳\",\"home\":\"123\"},{\"city\":\"邵阳2\",\"home\":\"3333\"},{\"city\":\"邵阳3\",\"home\":\"afs\"}]",Address.class);
System.out.println(addressList.get(1).getCity());
assertEquals(3,addressList.size());
}
输出:
red
邵阳2
@Test
public void toBean2(){
Farmer farmer = FFJson.toBean("{\"appleList\":[{\"color\":\"red\",\"mature\":true,\"price\":4.2,\"sweetness\":7,\"weight\":2.0,\"radius\":5.0},{\"color\":\"green\",\"mature\":false,\"price\":2.2,\"sweetness\":2,\"weight\":1.2,\"radius\":3.0}],\"address\":{\"city\":\"邵阳\",\"home\":\"xxxx 111-12号\"},\"name\":\"猪皇\",\"age\":50}",Farmer.class);
System.out.println(FFJson.toJsonString(farmer));
System.out.println(farmer.getName());
assertEquals(farmer.getAppleList().get(0).getColor(),"red");
}
输出:
{"appleList":[{"color":"red","mature":true,"price":4.2,"sweetness":7,"weight":2.0,"radius":5.0},{"color":"green","mature":false,"price":2.2,"sweetness":2,"weight":1.2,"radius":3.0}],"address":{"city":"邵阳","home":"xxxx 111-12号"},"name":"猪皇","age":50}
猪皇
至少这个使用方式看上去与市面上流行的json库一般无二了,当然内部是绣花枕头,也仅仅是练练手而已。