编译原理练手之撸个Json Parser(二) 实体类映射

上一篇中,实现了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库一般无二了,当然内部是绣花枕头,也仅仅是练练手而已。



猜你喜欢

转载自blog.csdn.net/lucytheslayer/article/details/80405625