├── README.md
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── .gitignore
├── src
├── main
│ └── java
│ │ └── com
│ │ └── paincker
│ │ └── utils
│ │ ├── data
│ │ ├── gson
│ │ │ ├── IAfterDeserializeAction.java
│ │ │ ├── IDataValidateAction.java
│ │ │ ├── NonNullField.java
│ │ │ ├── GsonUtils.java
│ │ │ ├── NonNullFieldConstructor.java
│ │ │ ├── DeserializeActionFactory.java
│ │ │ └── NonNullFieldFactory.java
│ │ └── ItemNonNullList.java
│ │ ├── L.java
│ │ └── StringUtils.java
└── test
│ └── java
│ └── com
│ └── paincker
│ └── testcase
│ ├── utils
│ └── TestDeepToString.java
│ └── gson
│ ├── TestNonNullField.java
│ └── TestDeserializeAction.java
├── gradle.properties
├── gradlew.bat
└── gradlew
/README.md:
--------------------------------------------------------------------------------
1 | # GsonStudy
2 |
3 | Gson TypeAdapter使用技巧几例:数据免判空、解析后校验、预处理
4 | http://www.paincker.com/gson-technic
5 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jzj1993/GsonStudy/HEAD/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea
5 | .DS_Store
6 | /build
7 | /out
8 | /captures
9 | .externalNativeBuild
10 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Wed Nov 22 10:59:26 CST 2017
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-all.zip
7 |
--------------------------------------------------------------------------------
/src/main/java/com/paincker/utils/data/gson/IAfterDeserializeAction.java:
--------------------------------------------------------------------------------
1 | package com.paincker.utils.data.gson;
2 |
3 | /**
4 | * 自动解析完成后,对数据进行处理(在数据解析线程工作)。
5 | * 注意,只有当数据是由Json解析而来且非空时,才会执行Action。
6 | * Created by jzj on 2017/11/22.
7 | */
8 | public interface IAfterDeserializeAction {
9 |
10 | void doAfterDeserialize();
11 | }
12 |
--------------------------------------------------------------------------------
/src/main/java/com/paincker/utils/data/gson/IDataValidateAction.java:
--------------------------------------------------------------------------------
1 | package com.paincker.utils.data.gson;
2 |
3 | /**
4 | * 自动解析完成后,对数据进行校验。如果是无效数据,则直接解析为null,从而自动剔除(在数据解析线程工作)。
5 | * 注意,只有当数据是由Json解析而来且非空时,才会执行Action。
6 | * Created by jzj on 2017/11/22.
7 | */
8 | public interface IDataValidateAction {
9 |
10 | boolean isDataValid();
11 | }
12 |
--------------------------------------------------------------------------------
/src/main/java/com/paincker/utils/data/gson/NonNullField.java:
--------------------------------------------------------------------------------
1 | package com.paincker.utils.data.gson;
2 |
3 | import com.google.gson.InstanceCreator;
4 |
5 | import java.lang.annotation.ElementType;
6 | import java.lang.annotation.Retention;
7 | import java.lang.annotation.RetentionPolicy;
8 | import java.lang.annotation.Target;
9 |
10 | /**
11 | * 解析后不会为null的NonNullField,不需要判空。注意,只有当对象自身解析非空,其NonNullField才会被填充。
12 | * Created by jzj on 2017/11/29.
13 | */
14 | @Retention(RetentionPolicy.RUNTIME)
15 | @Target(ElementType.FIELD)
16 | public @interface NonNullField {
17 | Class extends InstanceCreator> value() default NonNullFieldConstructor.class;
18 | }
19 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 |
3 | # IDE (e.g. Android Studio) users:
4 | # Gradle settings configured through the IDE *will override*
5 | # any settings specified in this file.
6 |
7 | # For more details on how to configure your build environment visit
8 | # http://www.gradle.org/docs/current/userguide/build_environment.html
9 |
10 | # Specifies the JVM arguments used for the daemon process.
11 | # The setting is particularly useful for tweaking memory settings.
12 | org.gradle.jvmargs=-Xmx1536m
13 |
14 | # When configured, Gradle will run in incubating parallel mode.
15 | # This option should only be used with decoupled projects. More details, visit
16 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
17 | # org.gradle.parallel=true
18 |
--------------------------------------------------------------------------------
/src/main/java/com/paincker/utils/L.java:
--------------------------------------------------------------------------------
1 | package com.paincker.utils;
2 |
3 |
4 | import java.util.IllegalFormatException;
5 |
6 | /**
7 | * Created by jzj on 2017/11/22.
8 | */
9 | public class L {
10 |
11 | public static void d(String tag, String msg, Object... args) {
12 | System.out.println("[" + tag + "] " + formatMessage(msg, args));
13 | }
14 |
15 | public static void e(String tag, String msg, Object... args) {
16 | System.err.println("[" + tag + "] " + formatMessage(msg, args));
17 | }
18 |
19 | public static void e(Throwable throwable) {
20 | throwable.printStackTrace();
21 | }
22 |
23 | private static String formatMessage(String msg, Object... args) {
24 | if (msg != null && msg.length() > 0 && args != null && args.length > 0) {
25 | try {
26 | return String.format(msg, args);
27 | } catch (IllegalFormatException e) {
28 | L.e(e);
29 | }
30 | }
31 | return msg;
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/main/java/com/paincker/utils/data/gson/GsonUtils.java:
--------------------------------------------------------------------------------
1 | package com.paincker.utils.data.gson;
2 |
3 | import com.google.gson.Gson;
4 | import com.google.gson.GsonBuilder;
5 | import com.google.gson.reflect.TypeToken;
6 |
7 | /**
8 | * Created by jzj on 2017/11/22.
9 | */
10 | public class GsonUtils {
11 |
12 | public static final String TAG = "Gson";
13 |
14 | private static final Gson gson;
15 |
16 | static {
17 | // 优先级高的后注册
18 | gson = new GsonBuilder()
19 | .registerTypeAdapterFactory(new DeserializeActionFactory())
20 | .registerTypeAdapterFactory(new NonNullFieldFactory())
21 | .create();
22 | }
23 |
24 | public static Gson getGson() {
25 | return gson;
26 | }
27 |
28 | public static T fromJson(String json, Class clazz) {
29 | return gson.fromJson(json, clazz);
30 | }
31 |
32 | public static T fromJson(String json, TypeToken typeToken) {
33 | return gson.fromJson(json, typeToken.getType());
34 | }
35 |
36 | public static String toJson(T obj) {
37 | return gson.toJson(obj);
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/test/java/com/paincker/testcase/utils/TestDeepToString.java:
--------------------------------------------------------------------------------
1 | package com.paincker.testcase.utils;
2 |
3 | import com.paincker.utils.StringUtils;
4 | import org.junit.Test;
5 |
6 | import java.util.*;
7 |
8 | /**
9 | * Created by jzj on 2017/12/1.
10 | */
11 | public class TestDeepToString {
12 |
13 | @Test
14 | public void testDeepToString() {
15 | System.out.println(StringUtils.objectDeepToString(new Data()));
16 | }
17 |
18 | public static class BaseData {
19 | private final Integer integer = 1;
20 | private final boolean bool = true;
21 | protected T type;
22 | }
23 |
24 | public static class Data extends BaseData {
25 |
26 | private final String string1 = null;
27 | private final String string2 = "";
28 |
29 | private final String[] array = new String[]{"p", "q", "r"};
30 | private final String[][][] multiArray = new String[][][]{
31 | {{"1", "2", "3"}, {"7", "8", "9"}}, {{"a", "b", "c"}, {"x", "y", "z"}}
32 | };
33 |
34 | private final List users = new ArrayList<>();
35 |
36 | private final List list = Arrays.asList("x", "y");
37 | private final LinkedList linkedList = new LinkedList<>();
38 | private final Map map = new HashMap<>();
39 | private final List> lists = new ArrayList<>();
40 |
41 | public Data() {
42 | type = "type";
43 | lists.add(Arrays.asList("1", "2", "3"));
44 | lists.add(Arrays.asList("a", "b", "c"));
45 | map.put("key1", "val1");
46 | map.put("key2", "val2");
47 | map.put("key3", new User(0, "小王"));
48 | users.add(new User(1, "小明"));
49 | users.add(new User(2, "小红"));
50 | }
51 | }
52 |
53 | public static class User {
54 | private final long id;
55 | private final String name;
56 |
57 | public User(long id, String name) {
58 | this.id = id;
59 | this.name = name;
60 | }
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/src/main/java/com/paincker/utils/data/gson/NonNullFieldConstructor.java:
--------------------------------------------------------------------------------
1 | package com.paincker.utils.data.gson;
2 |
3 | import com.google.gson.InstanceCreator;
4 | import com.google.gson.internal.$Gson$Types;
5 | import com.google.gson.internal.ConstructorConstructor;
6 | import com.google.gson.reflect.TypeToken;
7 |
8 | import java.lang.reflect.Array;
9 | import java.lang.reflect.GenericArrayType;
10 | import java.lang.reflect.Type;
11 | import java.util.HashMap;
12 | import java.util.Map;
13 |
14 | /**
15 | * 默认的InstanceCreator,创建非空对象
16 | * Created by jzj on 2017/11/29.
17 | */
18 | public class NonNullFieldConstructor implements InstanceCreator
37 | * Created by jzj on 2017/11/29.
38 | */
39 | public class NonNullFieldFactory implements TypeAdapterFactory {
40 |
41 | private static final String ANNOTATION_NAME = NonNullField.class.getSimpleName();
42 | /**
43 | * 保存Type及其对应的NonNullField
44 | */
45 | private static final Map> fieldMap = new ConcurrentHashMap<>();
46 | /**
47 | * InstanceCreator缓存
48 | */
49 | private static final Map, InstanceCreator> creatorCache = new ConcurrentHashMap<>();
50 |
51 | @Override
52 | public TypeAdapter create(Gson gson, TypeToken typeToken) {
53 |
54 | List fields = findMatchedFields(typeToken);
55 | final Type type = typeToken.getType();
56 |
57 | // 如果找到了,则包裹一层Adapter
58 | if (fields != null && !fields.isEmpty()) {
59 | fieldMap.put(type, fields);
60 |
61 | final TypeAdapter delegate = gson.getDelegateAdapter(this, typeToken);
62 | log("create wrapper adapter, type = %s, find %d fields, delegate = %s", typeToken, fields.size(), delegate);
63 | return new TypeAdapter() {
64 | @Override
65 | public void write(JsonWriter out, T value) throws IOException {
66 | delegate.write(out, value);
67 | }
68 |
69 | @Override
70 | public T read(JsonReader in) throws IOException {
71 | T t = delegate.read(in);
72 | log(" finish read, data = %s, type = %s, delegate = %s", t, type, delegate);
73 | replaceNonNullFields(t, typeToken);
74 | return t;
75 | }
76 | };
77 | }
78 | return null;
79 | }
80 |
81 | private static void log(String msg, Object... args) {
82 | L.d(GsonUtils.TAG, "[NonNullFieldFactory] " + msg, args);
83 | }
84 |
85 | /**
86 | * 是否需要搜索Type中的Field
87 | */
88 | @SuppressWarnings("RedundantIfStatement")
89 | private static boolean shouldSearch(Class clazz) {
90 | // 跳过不需要搜索的类
91 | if (clazz == null || clazz == Object.class || clazz.isPrimitive() || clazz.isEnum() || clazz.isArray()) {
92 | log("skip search class %s", clazz);
93 | return false;
94 | }
95 | // 跳过Java和Android系统中的类
96 | String packageName = clazz.getPackage().getName();
97 | if (packageName.startsWith("java") || packageName.startsWith("android")) {
98 | log("skip search class %s by package", clazz);
99 | return false;
100 | }
101 | // 只匹配特定的类、跳过其他第三方库的类……
102 | return true;
103 | }
104 |
105 | /**
106 | * 找到某个Type中的NonNullField,包括继承的
107 | */
108 | private static List findMatchedFields(TypeToken typeToken) {
109 | List list = null;
110 | Class raw = typeToken.getRawType();
111 | while (shouldSearch(raw)) {
112 | Field[] fields = raw.getDeclaredFields();
113 | for (Field field : fields) {
114 | field.setAccessible(true);
115 | if (field.getAnnotation(NonNullField.class) != null) {
116 | if (list == null) {
117 | list = new ArrayList<>();
118 | }
119 | list.add(field);
120 | }
121 | }
122 | // 解析父类
123 | typeToken = TypeToken.get($Gson$Types.resolve(typeToken.getType(), typeToken.getRawType(), raw.getGenericSuperclass()));
124 | raw = typeToken.getRawType();
125 | }
126 | return list == null ? Collections.EMPTY_LIST : list;
127 | }
128 |
129 | /**
130 | * 解析Field的Type,处理泛型参数
131 | *
132 | * @param typeToken Field所在类的Type
133 | * @param field 要解析的Field
134 | */
135 | private static Type resolveFieldType(TypeToken typeToken, Field field) {
136 | return $Gson$Types.resolve(typeToken.getType(), typeToken.getRawType(), field.getGenericType());
137 | }
138 |
139 | /**
140 | * 填充对象中的NonNullField
141 | */
142 | private static void replaceNonNullFields(Object o, TypeToken typeToken) {
143 | if (o == null) {
144 | return;
145 | }
146 | // 对于嵌套注解的情况(NonNullField对应类型中又有NonNullField),
147 | // 由于Gson会先解析内部数据,其TypeAdapter已经创建,此处map可以取到值
148 | List fields = fieldMap.get(typeToken.getType());
149 | if (fields == null || fields.isEmpty()) {
150 | return;
151 | }
152 | for (Field field : fields) {
153 | try {
154 | Object fieldValue = field.get(o);
155 | if (fieldValue == null) {
156 | Object value = constructField(field, resolveFieldType(typeToken, field));
157 | if (value == null) {
158 | throw new RuntimeException(String.format("Create field %s for type %s failure",
159 | field.getName(), typeToken.getType()));
160 | }
161 | field.set(o, value);
162 | log(" --> set field '%s.%s' to '%s'", typeToken.getType().getTypeName(), field.getName(), value);
163 | }
164 | } catch (IllegalArgumentException | IllegalAccessException e) {
165 | L.e(e);
166 | }
167 | }
168 | }
169 |
170 | private static Object constructField(Field field, Type type) {
171 | NonNullField annotation = field.getAnnotation(NonNullField.class);
172 | Class extends InstanceCreator> creatorClass = annotation.value();
173 | InstanceCreator creator = getCreator(creatorClass);
174 | Object instance = creator.createInstance(type);
175 | replaceNonNullFields(instance, TypeToken.get(type));
176 | return instance;
177 | }
178 |
179 | private static synchronized InstanceCreator getCreator(Class extends InstanceCreator> creatorClass) {
180 | InstanceCreator creator = creatorCache.get(creatorClass);
181 | if (creator == null) {
182 | try {
183 | creator = creatorClass.newInstance();
184 | creatorCache.put(creatorClass, creator);
185 | } catch (InstantiationException | IllegalAccessException e) {
186 | throw new RuntimeException("InstanceCreator " + creatorClass + " create failure", e);
187 | }
188 | }
189 | return creator;
190 | }
191 | }
192 |
--------------------------------------------------------------------------------
/src/main/java/com/paincker/utils/StringUtils.java:
--------------------------------------------------------------------------------
1 | package com.paincker.utils;
2 |
3 | import java.io.*;
4 | import java.lang.reflect.Array;
5 | import java.lang.reflect.Field;
6 | import java.util.*;
7 |
8 | /**
9 | * Created by jzj on 2017/11/22.
10 | */
11 | public class StringUtils {
12 |
13 | private static final HashSet