在restful风格的架构中,服务端与客户的通信通常采用json的数据格式;然而当今越来越分工明确的前端与后端开发者在确定通信内容时往往难以确定一个具体的信息需求,尤其当服务端是基于javabean生成json时,后端开发人员不能精确把握每一个成员变量是否应该提供给前端使用,甚至当前端接收到一大串的json时面对大量的字段集合,特别是当里面含有大量的无关字段时,往往很难筛选出有用字段。
到这里,我们不觉开始考虑在生成json的过程中是否有一种方法可以将不需要的字段全部过滤掉,或者是只将我指定的字段放到json中去呢?如果是这样,我提供的json将无比的简洁,与前端(这里将不仅指浏览器,更有需要的可能是Android和iOS)的交互也会变得轻松很多,不是吗?这里我们将讨论生成一个简洁的json的方式。
首先,要生成json的同时过滤字段,我们必须要获取对象的成员变量,所以我们可能需要事先准备一些反射的方法,这两个方法可以有效第帮助我们获取到指定对象的固有成员变量以及从父类继承过来的属性,及其属性值:
/**
* 获取obj对象fieldName的Field
* @param obj
* @param fieldName
* @return
*/
public static Field getFieldByFieldName(Object obj, String fieldName) {
for (Class<?> superClass = obj.getClass();
superClass != Object.class;
superClass = superClass.getSuperclass()) {
try {
return superClass.getDeclaredField(fieldName);
} catch (NoSuchFieldException e) {
}
}
return null;
}
/**
* 获取obj对象fieldName的属性值
* @param obj
* @param fieldName
* @return
*/
public static Object getValueByFieldName(Object obj, String fieldName) {
Field field = getFieldByFieldName(obj, fieldName);
Object value = null;
if(field!=null) {
try {
if (field.isAccessible()) {
value = field.get(obj);
} else {
field.setAccessible(true);
value = field.get(obj);
field.setAccessible(false);
}
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
return value;
}
考虑到我需要指定json需要生成的字段项,所以我可能必须要提供一个相应字段的集合或者数组,而前提是我们应该先有一组待测试的实体类:
如果我们对实体类本身就有一个抽象,可能会有这样一个基类(注意我们应该将抽象类理解为一个不能直接使用的类):
public abstract class BaseBean {
private Long id;
public BaseBean() {}
public BaseBean(Long id) {this.id = id;}
//转换成json字符串
@Override
public String toString() {
return JSONObject.fromObject(this).toString();
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
}
在这个基类中,我们定义了一个id字段,并重写了 toString 方法,实现toString直接将实体类转换为json的字符串表示,无论是自己拼接字符串还是利用第三方库,这并不符合我们目前的需求,这个暂且搁置,我们继续准备测试用的实体类:
//员工
public class Emp extends BaseBean {
private String acc; //用户账号
private String pwd; //用户密码
private String name; //用户名称
private String email; //用户邮箱
private String phone; //用户电话
private Long idDept;
private Long idComp;
private Dept dept;
private Comp comp;
}
//部门
public class Dept extends BaseBean {
private String deptName;
private Long idComp;
private Comp comp;
private List<Emp> userList;
}
//公司
public class Comp extends BaseBean {
private String compName;
private Long idProv;
private List<Dept> deptList;
private List<Emp> empList;
}
由于我们只关注成员变量,这里我们不再展示getter和setter。
ok,针对这种实体类,我们可以很容易地写出这样一个方法:
public static <T> JSONObject toJson(T t, String[] fields) throws Exception {
if(t == null) return null;
JSONObject jsonObject = new JSONObject();
for (String f : fields) {
Field fobj = ReflectHelper.getFieldByFieldName(t, f);
Object obj = ReflectHelper.getValueByFieldName(t, f);
if(fobj != null) {
jsonObject.put(f, obj);
}
}
return jsonObject;
}
先测试一下,准备测试代码:
public static void main(String[] args) throws Exception {
//公司
Comp comp1 = new Comp(1l, "测试公司1", 123l);
Comp comp2 = new Comp(2l, "测试公司2", 124l);
//部门
Dept dept11 = new Dept(11l, "测试部门11", 1l, comp1);
Dept dept12 = new Dept(12l, "测试部门12", 1l, comp1);
List<Dept> deptList1 = new ArrayList<Dept>();
deptList1.add(dept11);
deptList1.add(dept12);
Dept dept21 = new Dept(13l, "测试部门21", 2l, comp2);
Dept dept22 = new Dept(14l, "测试部门22", 2l, comp2);
List<Dept> deptList2 = new ArrayList<Dept>();
deptList1.add(dept21);
deptList1.add(dept22);
//员工们
//1公司1部门
Emp emp111 = new Emp(21l, "111", "pwd", "name111", "email", "phone", 11l, 1l, dept11, comp1);
Emp emp112 = new Emp(22l, "112", "pwd", "name112", "email", "phone", 11l, 1l, dept11, comp1);
Emp emp113 = new Emp(23l, "113", "pwd", "name113", "email", "phone", 11l, 1l, dept11, comp1);
List<Emp> empList11 = new ArrayList<Emp>();
empList11.add(emp111);
empList11.add(emp112);
empList11.add(emp113);
//1公司2部门
Emp emp121 = new Emp(24l, "121", "pwd", "name121", "email", "phone", 12l, 1l, dept12, comp1);
Emp emp122 = new Emp(25l, "122", "pwd", "name122", "email", "phone", 12l, 1l, dept12, comp1);
Emp emp123 = new Emp(26l, "123", "pwd", "name123", "email", "phone", 12l, 1l, dept12, comp1);
List<Emp> empList12 = new ArrayList<Emp>();
empList11.add(emp121);
empList11.add(emp122);
empList11.add(emp123);
//1公司所有员工
List<Emp> empList1 = new ArrayList<Emp>();
empList1.addAll(empList11);
empList1.addAll(empList12);
//2公司1部门
Emp emp211 = new Emp(27l, "211", "pwd", "name211", "email", "phone", 13l, 2l, dept21, comp2);
Emp emp212 = new Emp(28l, "212", "pwd", "name212", "email", "phone", 13l, 2l, dept21, comp2);
List<Emp> empList21 = new ArrayList<Emp>();
empList11.add(emp211);
empList11.add(emp212);
//2公司2部门
Emp emp221 = new Emp(29l, "221", "pwd", "name221", "email", "phone", 14l, 2l, dept22, comp2);
Emp emp222 = new Emp(30l, "222", "pwd", "name222", "email", "phone", 14l, 2l, dept22, comp2);
List<Emp> empList22 = new ArrayList<Emp>();
empList11.add(emp221);
empList11.add(emp222);
//2公司所有员工
List<Emp> empList2 = new ArrayList<Emp>();
empList2.addAll(empList21);
empList2.addAll(empList22);
//完善list
comp1.setDeptList(deptList1);
comp1.setEmpList(empList1);
dept11.setUserList(empList11);
dept12.setUserList(empList12);
comp2.setDeptList(deptList2);
comp2.setEmpList(empList2);
dept21.setUserList(empList21);
dept22.setUserList(empList22);
//测试
System.out.println("dept11:" + JSONHelper.toJson(dept11, new String[] {"deptName", "compName", "userList"}).toString());
}
运行报错:There is a cycle in the hierarchy!告诉我死循环了,可是我们的代码并没有死循环的地方。仔细一看,原来是dept包含了comp,comp中又包含了deptList,两个对象互相包含,而JSONObject在put时需要将其中嵌套的类型全部解析,导致死循环,好一个陷阱。
我们先绕过这个陷阱,把之前完善list的内容全部注释掉,再运行一遍,结果如下:
dept11:{"deptName":"测试部门11"}
再加上comp字段:
System.out.println("dept11:" + JSONHelper.toJson(dept11, new String[] {"deptName", "comp", "compName", "userList"}).toString());
看看结果:
dept11:{"deptName":"测试部门11","comp":{"compName":"测试公司1","deptList":[],"empList":[],"id":1,"idProv":123}}
结果仍然不满足要求,因为我们仅仅遍历了 dept11 这个对象,里面当然只包含dept11对象的属性,这有点像浅拷贝,所以我们应该判断成员变量,如果是我们定义的实体类(即,所有类都是BaseBean的子类),则要进行递归调用,我们修改代码如下。
public static <T> JSONObject toJson(T t, String[] fields) throws Exception {
if(t == null) return null;
JSONObject jsonObject = new JSONObject();
for (String f : fields) {
Field fobj = ReflectHelper.getFieldByFieldName(t, f);
Object obj = ReflectHelper.getValueByFieldName(t, f);
if(fobj != null && obj != null) {
if(BaseBean.class.isAssignableFrom(fobj.getType())) {
//向下调取
JSONObject child = toJson(obj, fields);
jsonObject.put(f, child);
} else if(Collection.class.isAssignableFrom(fobj.getType())) {
Collection<?> list = (collection<?>) obj;
JSONArray arrayChild = new JSONArray();
for (Object object : list) {
JSONObject child = toJson(object, fields);
arrayChild.add(child);
}
jsonObject.put(f, arrayChild);
} else {
jsonObject.put(f, obj);
}
}
}
return jsonObject;
}
测试结果:
dept11:{"deptName":"测试部门11","comp":{"compName":"测试公司1"}}
这个结果还好,但是还有一点不好,我们没有对list测试,为dept11添加一个userList11:
dept11.setUserList(empList11);
System.out.println("dept11:" + JSONHelper.toJson(dept11, new String[] {"id",
"deptName",
"comp", "compName",
"userList", "acc", "name"}).toString());
dept11: {
"id": 11,
"deptName": "测试部门11",
"comp": {
"id": 1,
"compName": "测试公司1"
},
"userList": [{
"id": 21,
"comp": {
"id": 1,
"compName": "测试公司1"
},
"acc": "111",
"name": "name111"
}, {
"id": 22,
"comp": {
"id": 1,
"compName": "测试公司1"
},
"acc": "112",
"name": "name112"
}, {
"id": 23,
"comp": {
"id": 1,
"compName": "测试公司1"
},
"acc": "113",
"name": "name113"
}, {
"id": 24,
"comp": {
"id": 1,
"compName": "测试公司1"
},
"acc": "121",
"name": "name121"
}, {
"id": 25,
"comp": {
"id": 1,
"compName": "测试公司1"
},
"acc": "122",
"name": "name122"
}, {
"id": 26,
"comp": {
"id": 1,
"compName": "测试公司1"
},
"acc": "123",
"name": "name123"
}, {
"id": 27,
"comp": {
"id": 2,
"compName": "测试公司2"
},
"acc": "211",
"name": "name211"
}, {
"id": 28,
"comp": {
"id": 2,
"compName": "测试公司2"
},
"acc": "212",
"name": "name212"
}, {
"id": 29,
"comp": {
"id": 2,
"compName": "测试公司2"
},
"acc": "221",
"name": "name221"
}, {
"id": 30,
"comp": {
"id": 2,
"compName": "测试公司2"
},
"acc": "222",
"name": "name222"
}
]
}
本以为会返回一个空的userList,结果是这样,原来emp员工对象里面也保存了公司的信息,这说明要生成的字段,如果有重复的会统统都给你遍历出来。
到这里,我们队json对象的过滤已经基本完成了,但是还有一个重要的问题,如果需要过滤的是一个集合?
public static <T> JSONArray toJson(Collection<T> collection, String[] fields) {
JSONArray array = new JSONArray();
if(collection == null || collection.size() == 0) {return array;}
for (T t : collection) {
JSONObject obj = new JSONObject();
try {
obj = toJson(t, fields);
} catch (Exception e) {
e.printStackTrace();
}
array.add(obj);
}
return array;
}
总结
本次尝试使用到了反射,递归等知识。从测试过程来看,基本已经满足我们的初期目标,但是还有几点是不可忽视的:
- json在生成的过程中很有可能会产生不可控的死循环,这是因为实体类之间互相包含,A中引用了B,B中又引用了A,在生成json时大家都应该警惕;
- json在生成的过程中只能遍历到某个实体类后才能判断并继续使用递归到这个实体类中继续遍历是否有需要的字段,所以如果我们想在dept中找到comp的compName属性,必须在fields数组中提供comp,否则程序将不能找到compName这个属性;
- 整个过程采用递归调用的方式,对资源的消耗是不可忽视的问题,所以这个方法仅适合用在实体类的嵌套层级比较浅的情况下,如果嵌套的过多有可能会产生内存崩溃的后果;
- 使用这个方式过滤,必须指定每一个我们需要的成员变量,如果成员变量过多,十几二十个字段是非常轻易就能达到的数量级,如果需要一个一个去敲,我实在无法想象这是一个怎样的灾难,所以我们可能会需要一种更加优化的处理方式,同时也希望大家能提出更加宝贵的意见。