Jsonview4
0. 文档传送
1. 简介
Jsonview框架构建于jackson框架之上,实现通过XML文件配置来自定义json格式,大大提升了java生成json字符串的自由性,让开发模块化更加便捷快速。
1.1. 运行环境
JDK8及以上
1.2. 使用方式
maven
<dependency>
	<groupId>com.github.developframework</groupId>
	<artifactId>jsonview4-core</artifactId>
	<version>4.0.2</version>
</dependency> 
1.3. 依赖项
- commons-lang3.jar
- slf4j-api.jar
- jackson-core.jar
- jackson-databind.jar
- jackson-annotations.jar
- lombok.jar
- expression.jar
2. HelloWorld
一个最简单的jsonview使用示例:
public class Application {
    public static void main(String[] args) {
        JsonviewFactory factory = new JsonviewFactory("/jsonview/jsonview-demo.xml");
        JsonProducer jsonProducer = factory.getJsonProducer();
        DataModel dataModel = new HashDataModel();
        dataModel.putData("sayHello", "Hello Jsonview4!");
        String json = jsonProducer.createJson(dataModel, "jsonview-demo", "first-view");
        System.out.println(json);
    }
} 
你需要一份jsonview XML配置,位置在上述声明的/jsonview/jsonview-demo.xml:
<jsonview-configuration xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                        xmlns="https://github.com/developframework/jsonview4/schema"
                        xsi:schemaLocation="
	https://github.com/developframework/jsonview4/schema
	https://github.com/developframework/jsonview4/schema/jsonview-configuration-4.0.xsd">
    <template-package namespace="jsonview-demo">
        <template id="first-view">
            <property data="sayHello"/>
        </template>
      
    </template-package>
</jsonview-configuration> 
运行结果:
{"sayHello":"Hello Jsonview4!"} 
3. 概览
3.1. java概览
3.1.1. DataModel
com.github.developframework.jsonview.data.DataModel接口是jsonview框架的数据模型。用于装载需要在json视图中渲染的数据或函数接口实现,数据由键值对构成。接口提供存入和取出数据的方法。 目前实现类仅有com.github.developframework.jsonview.data.HashDataModel 存取数据范例:
DataModel dataModel = new HashDataModel();
dataModel.putData("sayHello", "Hello Jsonview!");
Optional<Object> value = dataModel.getData("sayHello");
value.ifPresent(System.out::println); 
3.1.2. Expression
com.github.developframework.expression.Expression类是jsonview框架从DataModel中提取数据的表达式。不论dataModel存的是java实体类还是Map对象都可以使用表达式取值。 范例:
- student你可以从DataModel对象内取得名为student的对象
- #student你可以从DataModel对象内 强制从根路径 取得名为student的对象
- student.name你可以从DataModel对象内取得名为student的对象的name属性值
- students[0]你可以从DataModel对象内取得名为students的数组内的第1个元素
- student[0].name你可以从DataModel对象内取得名为students的数组内的第1个元素的name属性值
Expression 的详细使用请查看独立项目expression
3.1.3. JsonviewFactory
com.github.developframework.jsonview.core.JsonviewFactory类是jsonview框架的构建工厂。使用jsonview框架的第一步就是建立该对象。 建立该对象需要提供配置文件路径的字符串,多份配置文件可以采用字符串数组。
final String[] configs = {"config1.xml", "config2.xml"};
JsonviewFactory jsonviewFactory = new JsonviewFactory(configs); 
3.1.4. JsonviewConfiguration
com.github.developframework.jsonview.core.JsonviewConfiguration类为Jsonview框架的总配置文件,可以从JsonviewFactory中得到该对象。
JsonviewConfiguration jsonviewConfiguration = jsonviewFactory.getJsonviewConfiguration(); 
3.1.5. JsonProducer
com.github.developframework.jsonview.core.JsonProducer接口是json字符串建造类,执行一次生成json字符串的操作需要构建该对象。JsonProducer由JsonviewFactory生成。 该对象提供三个构建json字符串的方法:
public String createJson(DataModel dataModel, String namespace, String id, boolean isPretty); 
返回json字符串
- isPretty=true时可以美化json
public String createJson(DataModel dataModel, String namespace, String id); 
返回json字符串,不美化
public void printJson(JsonGenerator generator, DataModel dataModel, String namespace, String id); 
将json输出到ObjectMapper的generator对象
3.1.6. Template
com.github.developframework.jsonview.core.element.Template类,一个Template类的实例代表一种json的视图模板。它由namespace和id唯一确定。可以通过以下方法得到Template实例:
Template template = jsonviewConfiguration.extractTemplate("namespace", "id"); 
3.1.7. JsonviewTemplatePackage
com.github.developframework.jsonview.core.element.JsonviewTemplatePackage类,一个JsonviewTemplatePackage实例是一个命名空间,可以装载若干个Template实例。推荐将Template按功能有序存放于JsonviewTemplatePackage。通过以下方法得到JsonviewTemplatePackage对象:
JsonviewTemplatePackage jsonviewTemplatePackage = jsonviewConfiguration.getTemplatePackageByNamespace("namespace"); 
3.1.8. 异常
Jsonview框架的所有异常类。
| 异常 | 说明 | 
|---|---|
| JsonviewException | jsonview顶级异常 | 
| ConfigurationSourceException | 配置源异常 | 
| TemplateUndefinedException | template未定义异常 | 
| TemplatePackageUndefinedException | templatePackage未定义异常 | 
| JsonviewParseXmlException | 配置文件解析错误异常 | 
| LinkSizeNotEqualException | 使用link功能时数组大小不相等异常 | 
| ResourceNotUniqueException | 资源定义不唯一异常 | 
| InvalidArgumentsException | 无效的参数异常 | 
| DataUndefinedException | data没有定义在DataModel异常 | 
3.2. XML概览
3.2.1. 结构
Jsonview configuration 文档的结构如下:
<jsonview-configuration>
    <template-package namespace="">
    	<template id="">
    		<!-- 定义视图内容 -->
    	</template>
    	<template id="">
    		<!-- 定义视图内容 -->
    	</template>
    	<!-- 其它jsonview -->
    </template-package>
    <!-- 其它template-package -->
</jsonview-configuration> 
在<jsonview-configuration>节点中你可以配置任意数量的<template-package>,代表不同的模板包,在<template-package>节点上你必须声明命名空间namespace属性,并且namespace是唯一的,不然会抛出ResourceNotUniqueException。
在每个<template-package>节点中你可以配置任意数量的<template>。每个<template>即代表了某一种json格式的视图,在<template>节点你必须声明id属性,并且id必须是唯一的,不然会抛出ResourceNotUniqueException。
Jsonview configuration文档不是唯一的,Jsonview框架允许你拥有多份的Jsonview configuration配置文档,文档的加载顺序不分先后。
3.2.2. 标签
基本型标签
- <template>
- <object>
- <array>
- <property>
- <prototype>
功能型标签
- <include>
- <link>
- <relevance>
- <object-virtual>
- <property-ignore>
- <extend-port>
- <if>、- <else>
拓展型标签
-  <property-date>
-  <property-unixtimestamp>
-  <property-boolean>
3.2.2.1. 基本型标签
a) template
当你需要声明一个json格式模板时,你将会使用到<template>标签。
<template id="" data="" for-class=""></jsonview> 
| 属性 | 功能 | 是否必须 | 
|---|---|---|
| id | 声明模板编号,在命名空间中唯一 | 是 | 
| data | 取值表达式 | 否 | 
| for-class | 声明data表达式指向的对象类型 | 否 | 
| extend | 声明继承的jsonview和端口,格式为namespace.id:port(namespace不填时默认为当前namespace) | 否 | 
| map-function | 仅当data指代的数据为数组或List时有效。MapFunction的实现类全名或Expression表达式。详见5.1.2节 | 否 | 
b) object
当你需要在json中构建一个对象结构时,你将会使用到<object>标签。详见4.1.节
<object data="" alias="" for-class="" null-hidden="true"></object> 
| 属性 | 功能 | 是否必须 | 
|---|---|---|
| data | 取值表达式 | 是 | 
| alias | 别名,你可以重新定义显示名 | 否 | 
| for-class | 声明data表达式指向的对象类型 | 否 | 
| null-hidden | true时表示表达式取的值为null时隐藏该节点,默认为false | 否 | 
c) array
当你需要在json中构建一个数组结构时,你将会使用到<array>标签。详见4.6.节
<array data="" alias="" for-class="" null-hidden="true"></array> 
| 属性 | 功能 | 是否必须 | 
|---|---|---|
| data | 取值表达式 | 是 | 
| alias | 别名,你可以重新定义显示名 | 否 | 
| for-class | 声明data表达式指向的对象类型 | 否 | 
| null-hidden | true时表示表达式取的值为null时隐藏该节点,默认为false | 否 | 
| map-function | MapFunction的实现类全名或Expression表达式。详见5.1.2节 | 否 | 
<array>标签可以没有子标签,这时表示数组为基本类型数组。
d) property
当你需要在json中构建一个普通属性结构时, 你将会使用到<property>标签。
<property data="" alias="" converter="" null-hidden="true"/> 
| 属性 | 功能 | 是否必须 | 
|---|---|---|
| data | 取值表达式 | 是 | 
| alias | 别名,你可以重新定义显示名 | 否 | 
| converter | 类型转换器全限定类名或expression表达式。详见5.1.1节 | 否 | 
| null-hidden | true时表示表达式取的值为null时隐藏该节点,默认为false | 否 | 
e) prototype
使用Jackson原型实体构建结构, 你将会使用到<prototype>标签。详见4.7.节
<prototype data="" alias="" converter="" null-hidden="true"/> 
| 属性 | 功能 | 是否必须 | 
|---|---|---|
| data | 取值表达式 | 是 | 
| alias | 别名,你可以重新定义显示名 | 否 | 
| converter | 类型转换器全限定类名或expression表达式。详见5.1.1节 | 否 | 
| null-hidden | true时表示表达式取的值为null时隐藏该节点,默认为false | 否 | 
3.2.2.2. 功能型标签
a) include
Jsonview框架提供模块化设计json结构视图的功能。在一个<template>标签中你可以采用<include>标签来导入其它的<template>的结构内容,从而实现模块化单元分解。详见5.3.1.节
<include id="" namespace=""/> 
| 属性 | 功能 | 是否必须 | 
|---|---|---|
| id | 需要导入的jsonview id | 是 | 
| namespace | jsonview的命名空间,不填默认为当前命名空间 | 否 | 
b) link
该标签用于实现一对一链接对象功能。详见5.4.1.节。
<link data="" alias="" for-class="" null-hidden="true"></link> 
| 属性 | 功能 | 是否必须 | 
|---|---|---|
| data | 取值表达式,data必须指代一个List或array类型的对象 | 是 | 
| alias | 别名,你可以重新定义显示名 | 否 | 
| for-class | 声明data表达式指向的对象类型 | 否 | 
| null-hidden | true时表示表达式取的值为null时隐藏该节点,默认为false | 否 | 
c) relevance
该标签用于实现一对多关联功能。详见5.4.2.节。
<relevance data="" alias="" rel-function="" null-hidden="true"></relevance> 
| 属性 | 功能 | 是否必须 | 
|---|---|---|
| data | 取值表达式,data必须指代一个List或array类型的对象 | 是 | 
| alias | 别名,你可以重新定义显示名 | 否 | 
| rel-function | 关联判定器全限定类名或expression表达式 | 是 | 
| null-hidden | true时表示表达式取的值为null时隐藏该节点,默认为false | 否 | 
| map-function | MapFunction的实现类全名或Expression表达式。详见5.1.2节 | 否 | 
d) object-virtual
该标签用于虚拟对象结构。详见5.2.节
<object-virtual alias=""></object-virtual> 
| 属性 | 功能 | 是否必须 | 
|---|---|---|
| alias | 别名 | 是 | 
e) property-ignore
忽略属性,需结合for-class属性使用。详见4.5.节
<property-ignore name=""/> 
| 属性 | 功能 | 是否必须 | 
|---|---|---|
| name | 类中忽略的属性名称 | 是 | 
f) extend-port
此标签位置作为子<template>的接入位置。详见5.3.2节
<extend-port port-name=""/> 
| 属性 | 功能 | 是否必须 | 
|---|---|---|
| port-name | 端口名称 | 是 | 
g) if else
分支结构标签。详见5.5.1节
<if condition="">
</if>
<else>
</else> 
| 属性 | 功能 | 是否必须 | 
|---|---|---|
| condition | 条件接口实现类全名或Expression表达式 | 是 | 
3.2.2.3. 拓展型标签
a) property-date
该标签拓展于<property>,针对时间日期类型,使用pattern属性来格式化日期时间。详见4.3.1.节
| 拓展属性 | 功能 | 是否必须 | 
|---|---|---|
| pattern | 格式化模板字符串,不填默认为"yyyy-MM-dd HH:mm:ss" | 否 | 
支持的时间日期类型:
- java.util.Date
- java.util.Calendar
- java.sql.Date
- java.sql.Time
- java.sql.Timestamp
- (JDK8) java.time.LocalDate
- (JDK8) java.time.LocalDateTime
- (JDK8) java.time.LocalTime
- (JDK8) java.time.Instant
b) property-unixtimestamp
该标签拓展于<property>,针对时间日期类型,可以将时间日期类型转化为unix时间戳格式显示。可支持的时间日期类型同<property-date>。详见4.3.2.节
c) property-boolean
该标签拓展于<property>,可以将数字类型(short、int、long)变为boolean型,非0值为true,0值为false。详见4.3.3节
4. 基本使用
模型声明(以下各小节示例代码均使用这些模型实体类):
// 学生类 Student.java
@Data
public class Student {
    // 编号
    private int id;
    // 学生名称
    private String name;
    // 班级ID
    private int classId;
    // 出生日期
    private Date birthday;
    public Student(int id, String name, int classId, String birthday) {
        this.id = id;
        this.name = name;
        this.classId = classId;
        if(birthday != null) {
            DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
            try {
                this.birthday = dateFormat.parse(birthday);
            } catch (ParseException e) {
                e.printStackTrace();
            }
        }
    }
} 
// 账户类 Account.java   一个学生对应有一个账户
@Data
@AllArgsConstructor
public class Account {
    private String username;
    private String password;
} 
// 班级类 SchoolClass.java   一个班级对应多个学生
@Data
@AllArgsConstructor
public class SchoolClass {
    private int id;
    private String className;
} 
4.1. 简单输出模型对象Json
<!-- /jsonview/jsonview-student.xml --> 
<!-- 忽略jsonview-configuration -->
<template-package namespace="jsonview-student">
  <template id="student-detail" data="student">
    <property data="id"/>
    <property data="name"/>
    <property data="classId"/>
    <property data="birthday"/>
  </template>
</template-package> 
// Application.java    main()
JsonviewFactory factory = new JsonviewFactory("/jsonview/jsonview-student.xml");
JsonProducer jsonProducer = factory.getJsonProducer();
DataModel dataModel = new HashDataModel();
Student peter = new Student(1, "Peter", 1, "1995-01-01");
dataModel.putData("student", peter);
String json = jsonProducer.createJson(dataModel, "jsonview-student", "student-detail");
System.out.println(json); 
执行结果:
{"id":1,"name":"Peter","classId":1,"birthday":"Sun Jan 01 00:00:00 CST 1995"} 
4.2. 使用alias修改显示名称
<!-- /jsonview/jsonview-student.xml --> 
<!-- 忽略jsonview-configuration -->
<template-package namespace="jsonview-student">
  <template id="student-detail" data="student">
    <property data="id" alias="student_id"/>
    <property data="name" alias="student_name"/>
    <property data="classId" alias="student_classId"/>
    <property data="birthday" alias="student_birthday"/>
  </template>
</template-package> 
{"student_id":1,"student_name":"Peter","student_classId":1,"student_birthday":"Sun Jan 01 00:00:00 CST 1995"} 
4.3. property扩展
4.3.1 使用property-date格式化日期时间
该标签可以格式化时间。 把4.1.节代码
<property data="birthday"/> 
替换为
<property-date data="birthday" pattern="yyyy-MM-dd"/> 
运行结果:
{"id":1,"name":"Peter","classId":1,"birthday":"1995-01-01"} 
4.3.2 使用property-unixtimestamp输出时间戳
该标签可以使日期时间类型转化成时间戳形式输出。
dataModel.putData("datetime", LocalDateTime.of(2016, 1, 1, 0, 0, 0)); 
{"datetime" : 1451577600} 
4.3.3 使用property-boolean转换
该标签可以把非0数字转换成true,0转换成false
DataModel dataModel = new HashDataModel();
dataModel.putData("number1", 1);
dataModel.putData("number2", 0); 
{"number1" : true, "number2" : false} 
4.4. 使用null-hidden隐藏值为null的属性
把4.1.节代码
<property data="birthday"/> 
替换为
<property data="birthday" null-hidden="true"/> 
student实例传入null的birthday值
Student student = new Student(1, "Peter", 1, null); 
运行结果:
{"id":1,"name":"Peter","classId":1} 
4.5. 使用for-class输出模型对象Json
<template id="student-detail" data="student" for-class="test.Student" /> 
运行结果和4.1.节相同。 使用property-ignore忽略不需要的属性:
<template id="student-detail" data="student" for-class="test.Student">
  <property-ignore name="birthday" />
</template> 
运行结果:
{"id":1,"name":"Peter","classId":1} 
4.6. 简单输出数组模型Json
利用array 标签构造一个数组结构:
<template id="student-list">
  <array data="students">
    <property data="name"/>
    <property data="classId"/>
    <property-date data="birthday" pattern="yyyy-MM-dd"/>
  </array>
</template> 
Student peter = new Student(1, "Peter", 1, "1995-01-01");
Student john = new Student(2, "John", 1, "1996-5-20");
Student[] students = {peter, john};
dataModel.putData("students", students);
// isPretty参数设为true,开启json美化
String json = jsonProducer.createJson(dataModel, "jsonview-student", "student-list", true); 
{
  "students" : [ {
    "id" : 1,
    "name" : "Peter",
    "classId" : 1,
    "birthday" : "1995-01-01"
  }, {
    "id" : 2,
    "name" : "John",
    "classId" : 1,
    "birthday" : "1996-05-20"
  } ]
} 
或者直接把data设定在<template> 标签上,Jsonview框架会自动识别data对应的数据是否是数组或List。
<template id="student-list" data="students">
  <property data="id"/>
  <property data="name"/>
  <property data="classId"/>
  <property-date data="birthday" pattern="yyyy-MM-dd"/>
</template> 
[ {
  "id" : 1,
  "name" : "Peter",
  "classId" : 1,
  "birthday" : "1995-01-01"
}, {
  "id" : 2,
  "name" : "John",
  "classId" : 1,
  "birthday" : "1996-05-20"
} ] 
4.7. 使用Jackson原型实体
使用<prototype> 标签可以使用原生的Jackson方式转化实体成json。
<template id="student-detail">
  <prototype data="student" />
</template> 
@Data
public class Student {
    // 编号
    private int id;
    // 学生名称
    @JsonProperty("student_name")	// 通过注解@JsonProperty对属性重命名
    private String name;
    // 班级ID
    @JsonIgnore		// 通过注解@JsonIgnore忽略该属性
    private int classId;
    // 出生日期
    @JsonFormat(locale="zh", timezone="GMT+8", pattern="yyyy-MM-dd")//通过@JsonFormat格式化日期
    private Date birthday;
    //构造方法略
} 
{
  "student" : {
    "id" : 1,
    "birthday" : "1995-01-01",
    "student_name" : "Peter"
  }
} 
更多注解使用请参考jackson-annotations文档。
5. 高级功能
5.1.处理器
5.1.1. <property> 的转换器 converter
 
com.github.developframework.jsonview.core.dynamic.PropertyConverter 接口可以对表达式选取的属性值进行自定义转换。
@FunctionalInterface
public interface PropertyConverter<TARGET> {
    /**
     * 转化方法
     * @param source 源对象
     * @return 目标对象
     */
    TARGET convert(Object source);
} 
- 泛型TARGET为转化后的类型。
例如将peter的name处理后输出:
<template id="student-detail" data="student">
  <property data="id" />
  <property data="name" converter="nameConverter"/>
  <property data="classId"/>
  <property-date data="birthday" pattern="yyyy-MM-dd"/>
</template> 
dataModel.putData("student", peter);
// 这里采用JDK8 lambda写法
dataModel.putData("nameConverter", (PropertyConverter<String>) source -> "My name is " + source); 
{
  "id" : 1,
  "name" : "My name is Peter",
  "classId" : 1,
  "birthday" : "1995-01-01"
} 
<property> 系列标签的converter 属性可以填写Expression表达式,还可以填写PropertyConverter 的接口实现类全类名。
5.1.2. <array> 的元素映射器 map-function
 
在<array>节点属性map-function用于指定对每个元素的映射函数。map-function的值可以为Expression表达式或一个实现com.github.developframework.jsonview.core.dynamic.MapFunction接口的完全类名。其中使用表达式方式,其获取的实例必须是com.github.developframework.jsonview.core.dynamic.MapFunction的实现类。具体示例:实现功能:对数组的每个元素进行映射处理,处理结果作为生成Json的数据。以下示例在字符串数组的每项元素以value_{item}_{i} 形式输出。
String[] strArray = new String[]{"aaa", "bbb", "ccc"};
dataModel.putData("strArray", strArray);
dataModel.putData("func", ((MapFunction<String, String>) (item, i) -> "value_" + item + "_" + i)); 
<template id="array-map-function-demo" >
  <array data="strArray" map-function="func"></array>
</template> 
{"str_array":["value_aaa_0","value_bbb_1","value_ccc_2"]} 
注意
使用map-function会导致<array>标签的所有子节点失效,因为映射结果将会直接作为json数据。
如果你设置了子节点将会出现以下警告:
The child element invalid, because you use "map-function" attribute.
5.2. 虚拟结构
使用<object-virtual>可以虚拟一个对象结构。 利用仅有的属性值,构造一个对象结构:
<template id="student-detail">
  <object-virtual alias="student">
    <property data="id"/>
    <property data="name"/>
    <property data="classId"/>
    <property-date data="birthday" pattern="yyyy-MM-dd"/>
  </object-virtual>
</template> 
dataModel.putData("id", 1);
dataModel.putData("name", "Peter");
dataModel.putData("classId", 1);
dataModel.putData("birthday", "1995-01-01"); 
{
  "student" : {
    "id" : 1,
    "name" : "Peter",
    "classId" : 1,
    "birthday" : "1995-01-01"
  }
} 
5.3. 模块化
5.3.1 引用
使用<include>标签引用其它<template>模板,从而可实现模块化设计,避免重复定义视图模板。
<template-package namespace="jsonview-student">
  <template id="student-list" data="students">
    <include id="student-detail" />
  </template>
  <template id="student-detail">
    <property data="id"/>
    <property data="name"/>
    <property data="classId"/>
    <property-date data="birthday" pattern="yyyy-MM-dd"/>
  </template>
</template-package> 
5.3.2 继承
Jsonview框架的继承的概念,在<template>标签可以添加属性extend指定继承的template和继承的端口。继承的概念可以理解为反向include,调用子template视图,会优先从父template开始构造结构,当遇到匹配端口名的<extend-port>标签时才会构造子template视图。
**注意:**假如单独调用了有<extend-port>标签的父template视图或者端口没有与之对应的子template实现,则<extend-port>标签被忽略。
<template-package namespace="jsonview-student">
  <!-- 一个父视图模板  -->
  <template id="student-parent">
    <object-virtual alias="other">
      <property data="otherData" />
    </object-virtual>
    <!-- 子视图模板的内容将会插入到这个端口位置上 -->
    <extend-port port-name="my-port" />
  </template>
  <!-- 子视图模板  -->
  <template id="student-detail" extend="student-parent:my-port">
    <!-- 本模板内容将会插入到父视图模板的my-port端口位置上 -->
    <object data="student">
      <property data="id"/>
      <property data="name"/>
      <property data="classId"/>
      <property-date data="birthday" pattern="yyyy-MM-dd"/>
    </object>
  </template>
</template-package> 
Student peter = new Student(1, "Peter", 1, "1995-01-01");
dataModel.putData("student", peter);
dataModel.putData("otherData", "I'm other data.");
// 这里调用的子视图模板
String json = jsonProducer.createJson(dataModel, "jsonview-student", "student-detail", true); 
{
  "other" : {
    "otherData" : "I'm other data."
  },
  "student" : {
    "id" : 1,
    "name" : "Peter",
    "classId" : 1,
    "birthday" : "1995-01-01"
  }
} 
extend 属性的写法为 namespace.templateId:portName 其中namespace可以省略,默认为当前命名空间下。
5.4. 链接与关联
5.4.1. 一对一数组链接
使用<link> 标签可以在数组间一对一链接对象。**该标签仅能在<array>下使用。**当<link> 的data属性所指的数组和父<array>数组个数不相同时将会抛出LinkSizeNotEqualException。 例子: 假如每个学生实例都有一个账户实例,并且又都一对一对应了一个成绩值。
<template-package namespace="jsonview-student">
  <template id="student-list" data="students">
    <property data="id"/>
    <property data="name"/>
    <property data="classId"/>
    <property-date data="birthday" pattern="yyyy-MM-dd"/>
    <!-- 一对一对应accounts数组每项 -->
    <link data="#accounts" alias="account">
      <!-- 引用另一个命名空间的模板 -->
      <include id="account-detail" namespace="jsonview-account"/>
    </link>
    <!-- 一对一对应scores数组每项 -->
    <link data="#scores" alias="score"/>
  </template>
</template-package>
<template-package namespace="jsonview-account">
  <template id="account-detail">
    <property data="username"/>
    <property data="password"/>
  </template>
</template-package> 
 Account peterAccount = new Account("peter's username", "peter's password");
Account johnAccount = new Account("john's username", "john's password");
Student[] students = {peter, john};
Account[] accounts = {peterAccount, johnAccount};
Integer[] scores = {95, 98};
dataModel.putData("students", students);
dataModel.putData("accounts", accounts);
dataModel.putData("scores", scores); 
[ {
  "id" : 1,
  "name" : "Peter",
  "classId" : 1,
  "birthday" : "1995-01-01",
  "account" : {
    "username" : "peter's username",
    "password" : "peter's password"
  },
  "score" : 95
}, {
  "id" : 2,
  "name" : "John",
  "classId" : 1,
  "birthday" : "1996-05-20",
  "account" : {
    "username" : "john's username",
    "password" : "john's password"
  },
  "score" : 98
} ] 
5.4.2. 根据条件关联
假如A数组有2个元素,B数组有3个元素。其中A[0] 需要关联B[0]和B[1], A[1] 需要关联B[2]。这种需求下就可以使用<relevance>标签,实现在数组间一对多关联。属性rel-function 指定判定条件,需要实现一个接口:
public interface RelFunction<S, T> {
    boolean relevant(S sourceItem, int sourceIndex, T target, int targetIndex);
} 
其中泛型S指代A数组类型,T指代B数组类型。
sourceItem是迭代了A数组的每一项,sourceIndex是它的索引。每一项的A元素都会去迭代B数组的每一项target,targetIndex是索引,relevant 方法返回true表示需要关联。
具体看示例,有如下数据结构关系:
// 1班
SchoolClass schoolClass1 = new SchoolClass(1, "1班");
// 2班
SchoolClass schoolClass2 = new SchoolClass(2, "2班");
// 1班的学生
Student peter = new Student(1, "Peter", 1, "1995-01-01");
Student john = new Student(2, "John", 1, "1996-5-20");
// 2班的学生
Student bill = new Student(3, "Bill", 2, "1993-4-16");
Student[] students = {peter, john, bill};
SchoolClass[] schoolClasses = {schoolClass1, schoolClass2};
dataModel.putData("students", students);
dataModel.putData("schoolClasses", schoolClasses); 
<template-package namespace="jsonview-student">
  <template id="student-detail">
    <property data="name"/>
    <property-date data="birthday" pattern="yyyy-MM-dd"/>
  </template>
</template-package>
<template-package namespace="jsonview-class">
  <template id="class-list" data="schoolClasses">
    <property data="id" />
    <property data="className" />
    <!-- 关联学生列表 -->
    <relevance data="#students" rel-function="rel-function">
      <include id="student-detail" namespace="jsonview-student" />
    </relevance>
  </template>
</template-package> 
实现RelFunction
dataModel.putData("rel-function", (RelFunction<SchoolClass, Student>) (sourceItem, sourceIndex, target, targetIndex) -> sourceItem.getId() == target.getClassId()); 
判定条件为当SchoolClass(sourceItem)的id与Student(target)的classId相等时,允许关联。
[ {
  "id" : 1,
  "className" : "1班",
  "students" : [ {
    "name" : "Peter",
    "birthday" : "1995-01-01"
  }, {
    "name" : "John",
    "birthday" : "1996-05-20"
  } ]
}, {
  "id" : 2,
  "className" : "2班",
  "students" : [ {
    "name" : "Bill",
    "birthday" : "1993-04-16"
  } ]
} ] 
5.5. 分支结构
5.5.1. <if> <else>
 
可以使用<if> <else> 标签进行模块内容的取舍。<else> 标签可以不写,但必须紧跟<if> 后出现。
<if> 标签的condition 属性内容为接口com.github.developframework.jsonview.core.dynamic.Condition 的实现类或直接使用Boolean类型。
@FunctionalInterface
public interface Condition {
    /**
     * 判断条件
     * @param dataModel 数据模型
     * @param expression 当前位置的表达式
     * @return 判断结果
     */
    boolean verify(DataModel dataModel, Expression expression);
} 
最简范例:
<template id="first-view">
  <if condition="myCondition">
    <property data="sayHello"/>
  </if>
  <else>
    <property data="sayBye"/>
  </else>
</template> 
dataModel.putData("sayHello", "Hello");
dataModel.putData("sayBye", "Bye");
dataModel.putData("myCondition", (Condition) (dm, expression) -> true);
// 或直接使用boolean
// dataModel.putData("myCondition", true); 
{"sayHello" : "Hello"} 
当myCondition 接口返回false时
{"sayBye" : "Bye"} 
6. 日志
Jsonview框架使用slf4j-api日志接口,提供内部日志打印功能。可以使用log4j或者logback打印日志。 以下示例使用logback
<configuration scan="true" scanPeriod="60 seconds" debug="false">
	<contextName>jsonview-log</contextName>
	<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
		<encoder>
			<pattern>%d{HH:mm:ss.SSS} %-5level - %msg%n
			</pattern>
		</encoder>
	</appender>
	<logger name="com.github.developframework.jsonview" level="INFO" additivity="false">
		<appender-ref ref="STDOUT" />
	</logger>
</configuration> 
项目启动日志:
09:29:07.753 INFO  - Jsonview framework loaded the configuration source "/jsonview/jsonview-demo.xml".
 JarCasting
 JarCasting