自定义解析过程
控制版本
gson提供了两个注解@Since和@Until来达到版本控制的目的。意义是有,不过从实地的经验看这两个注解的意义不大。比如两个部件A和B使用json格式的字符串来交换数据,无非是三种场景,A和B使用相同的编码、解码库,A使用的编码、解码库版本新于B使用的编码、解码库,A使用的编码、解码库旧于B使用的编码、解码库。第一种场景自然是没有问题,关键在于第二种和第三种场景。为了简单,这时假设A和B之间的交互协议在演进时只有增、删字段,没有修改字段名的操作,基于已有的使用经验,可以知道默认配置的gson完全可以满足要求,不需要作特别的处理。当然使用@Since来控制的话比较严格,比较准确,这是优点。
如下是@Since注解的使用样例。
import java.util.HashMap;
import java.util.Map;
import lombok.Data;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.annotations.Since;
public class JsonTest {
public static void main(final String[] args) {
// final Gson gson = new Gson();
final Gson gson = new GsonBuilder().setVersion(1.1).create();// 发送端的版本高于接收端
final Person jack1 = new Person();
jack1.setAge(30);
jack1.setName("Jackie");
jack1.putValue("email", "email");
jack1.putValue("phoneno", "phoneno");
final Person jack2 = new Person();
jack2.setAge(1);
jack2.setName("Jackie Boy");
jack2.putValue("email", "email");
jack2.putValue("phoneno", "phoneno");
final String json = gson.toJson(new Person[] { jack1, jack2 });
System.out.println(json);
final Gson gson2 = new GsonBuilder().setVersion(1.0).create();// 接收端的版本号低于发送端
final Person[] persons = gson2.fromJson(json, Person[].class);
for (final Person p : persons) {
System.out.println(p);
}
}
}
@Data
class Person {
@Since(1.0)
private String name;
@Since(1.1)
private int age;// age字段的版本号较大,表示是后来增加的字段
@Since(1.0)
private Map<String, Object> contact;
public Person() {
contact = new HashMap<String, Object>();
}
public void putValue(final String key, final Object value) {
contact.put(key, value);
}
}
样例的输出如下,可以看到当接收端版本低于发送端时,新版本中增加的字段没有被解析出来。
[{"name":"Jackie","age":30,"contact":{"email":"email","phoneno":"phoneno"}},{"name":"Jackie Boy","age":1,"contact":{"email":"email","phoneno":"phoneno"}}]
Person(name=Jackie, age=0, contact={email=email, phoneno=phoneno})
Person(name=Jackie Boy, age=0, contact={email=email, phoneno=phoneno})
@Until注解的使用同理,这里就不再举例。
控制输出字段的名称
方法一
比如为了复用已定义的Java Bean,来适配某个需求下的json格式定义,但已有Java Bean中成员的名称和json格式中字段的对应名称有差别,这时就需要在编码或者解码时做特别的处理,保证二者可以自然对应,减少对已有代码的改动。又或者json中字段的名称比较长或者包含有空格,这样在定义Java Bean的成员时多有不便,这时也需要对编码、解码的过程做控制,解决这个小问题。如下是样例,很简单,只是在类成员前增加了注解,因此只列类定义,其它代码参照前述样例。
@Data
class Person {
@Since(1.0)
@SerializedName("Qualified Name")// 指定name成员在输出时替换为Qualified Name
private String name;
@Since(1.1)
private int age;
@Since(1.0)
private Map<String, Object> contact;
public Person() {
contact = new HashMap<String, Object>();
}
public void putValue(final String key, final Object value) {
contact.put(key, value);
}
}
代码的输出如下。
[{"Qualified Name":"Jackie","age":30,"contact":{"email":"email","phoneno":"phoneno"}},{"Qualified Name":"Jackie Boy","age":1,"contact":{"email":"email","phoneno":"phoneno"}}]
Person(name=Jackie, age=0, contact={email=email, phoneno=phoneno})
Person(name=Jackie Boy, age=0, contact={email=email, phoneno=phoneno})
在使用@SerializedName时,需要注意它会覆盖枚举FieldNamingPolicy对字段格式的控制。
方法二
另外还可以通过定义字段名称输出策略处理器来解决成员名称与字段名映射的问题,这时要用到实现接口FieldNamingStrategy的策略类。如下是使用样例。
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
import lombok.Data;
import com.google.gson.FieldNamingStrategy;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.annotations.SerializedName;
import com.google.gson.annotations.Since;
public class JsonTest {
public static void main(final String[] args) {
final Gson gson = new GsonBuilder().setVersion(1.1).setFieldNamingStrategy(new PersonNamingStrategy()).create();// 指定使用策略来修改gson的默认行为
final Person jack1 = new Person();
jack1.setAge(30); jack1.setName("Jackie");
jack1.putValue("email", "email");
jack1.putValue("phoneno", "phoneno");
final Person jack2 = new Person(); jack2.setAge(1);
jack2.setName("Jackie Boy");
jack2.putValue("email", "email");
jack2.putValue("phoneno", "phoneno");
final String json = gson.toJson(new Person[] { jack1, jack2 });
System.out.println(json);
final Gson gson2 = new GsonBuilder().setVersion(1.0).setFieldNamingStrategy(new PersonNamingStrategy()).create(); // 解析时同样要设置策略,否则将有字段无法解析
final Person[] persons = gson2.fromJson(json, Person[].class);
for (final Person p : persons)
{
System.out.println(p);
}
}
}
@Data
class Person
{
@Since(1.0)
@SerializedName("Qualified Name")// 通过注解来控制字段的输出名称
private String name;
@Since(1.1) private int age;
@Since(1.0) private Map<String, Object> contact;// 这个成员没有使用注解来控制输出字段名称
private String address;
private String location;
public Person() {
contact = new HashMap<String, Object>();
}
public void putValue(final String key, final Object value) {
contact.put(key, value);
}
}
// 如下的策略实现修改了Person类中name和contact两个成员的输出字段。
class PersonNamingStrategy implements FieldNamingStrategy {
@Override
public String translateName(final Field f) {
if (f.getName().equals("name")) {
return "Name";
}
if (f.getName().equals("contact")) {
return "Contact";
} return f.getName();
}
}
样例代码的输出如下。
[{"Qualified Name":"Jackie","age":30,"Contact":{"email":"email","phoneno":"phoneno"}},{"Qualified Name":"Jackie Boy","age":1,"Contact":{"email":"email","phoneno":"phoneno"}}]
Person(name=Jackie, age=0, contact={email=email, phoneno=phoneno}, address=null, location=null)
Person(name=Jackie Boy, age=0, contact={email=email, phoneno=phoneno}, address=null, location=null)
从输出可以得出一个有趣的结论,在gson进行编码、解码时,注解@SerializedName("Qualified Name")的优先级要高于名称策略类中指定的名称。