...但我更希望图书馆尽其所能。
好吧,就用Gson吧。
有的是数据传输对象 https://en.wikipedia.org/wiki/Data_transfer_object模式,特别是 Gson 映射类,可以完美地解决您的问题。默认情况下,如果 Gson 能够通过内置设施满足映射,那么除特殊情况外,您不必自己完成它的工作。此类映射类仅存在于 JSON 内容和您的业务对象类之间,以便对数据进行(反)序列化(简单来说,DTO 仅出于此目的而存在,并且与 Gson 相关的注释不得传播到您的业务类上 -只需将 DTO 转换为业务对象)。
Mappings
final class Wrapper {
@SerializedName("id")
@Expose
private final String id = null;
@SerializedName("games")
@Expose
private final List<String> games = null;
@SerializedName("definition")
@Expose
private final FormulaDefinition formulaDefinition = null;
private Wrapper() {
}
@Override
public String toString() {
return new StringBuilder("Wrapper{")
.append("id='").append(id)
.append("', games=").append(games)
.append(", formulaDefinition=").append(formulaDefinition)
.append('}')
.toString();
}
}
package q41323887;
import java.util.List;
import com.google.gson.annotations.Expose;
import com.google.gson.annotations.SerializedName;
final class FormulaDefinition {
@SerializedName("count")
@Expose
private /*final*/ int count /*= 0*/; // Gson works with final primitives like `int` strangely
@SerializedName("operatorDefinitions")
@Expose
private final List<OperatorDefinition> operatorDefinitions = null;
private FormulaDefinition() {
}
@Override
public String toString() {
return new StringBuilder("FormulaDefinition{")
.append("count=").append(count)
.append(", operatorDefinitions=").append(operatorDefinitions)
.append('}')
.toString();
}
}
final class OperatorDefinition {
@SerializedName("operators")
@Expose
private final Operator operators = null;
@SerializedName("first")
@Expose
private final String first = null;
@SerializedName("second")
@Expose
private final String second = null;
@SerializedName("result")
@Expose
private final String result = null;
private OperatorDefinition() {
}
@Override
public String toString() {
return new StringBuilder("OperatorDefinition{")
.append("operators=").append(operators)
.append(", first='").append(first)
.append("', second='").append(second)
.append("', result='").append(result)
.append("'}")
.toString();
}
}
enum Operator {
PLUS("+"),
MINUS("-"),
ASTERISK("*"),
SLASH("/");
private static final Map<String, Operator> tokenToOperatorIndex = createTokenToOperatorIndexInJava8();
private final String token;
Operator(final String token) {
this.token = token;
}
static Operator resolveOperator(final String token)
throws NoSuchElementException {
final Operator operator = tokenToOperatorIndex.get(token);
if ( operator == null ) {
throw new NoSuchElementException("Cannot resolve operator by " + token);
}
return operator;
}
private static Map<String, Operator> createTokenToOperatorIndex() {
final Map<String, Operator> index = new HashMap<>();
for ( final Operator operator : values() ) {
index.put(operator.token, operator);
}
return unmodifiableMap(index);
}
private static Map<String, Operator> createTokenToOperatorIndexInJava8() {
final Map<String, Operator> index = Stream.of(values())
.collect(toMap(operator -> operator.token, identity()));
return unmodifiableMap(index);
}
}
反序列化
那么,自从你的operators
意味着是有效的枚举,这是您真正需要自定义 JSON 反序列化器的唯一地方,因为 Gson 默认规则不知道这些规则。
final class OperatorJsonDeserializer
implements JsonDeserializer<Operator> {
private static final JsonDeserializer<Operator> operatorJsonDeserializer = new OperatorJsonDeserializer();
private OperatorJsonDeserializer() {
}
static JsonDeserializer<Operator> getOperatorJsonDeserializer() {
return operatorJsonDeserializer;
}
@Override
public Operator deserialize(final JsonElement json, final Type type, final JsonDeserializationContext context)
throws JsonParseException {
try {
final String token = json.getAsJsonPrimitive().getAsString();
return resolveOperator(token);
} catch ( final NoSuchElementException ex ) {
throw new JsonParseException(ex);
}
}
}
Demo
现在你可以使用Wrapper
类来反序列化您的 JSON:
// Gson instances are thread-safe and can be easily instantiated once
private static final Gson gson = new GsonBuilder()
.registerTypeAdapter(Operator.class, getOperatorJsonDeserializer())
.create();
public static void main(final String... args)
throws IOException {
try ( final Reader reader = new InputStreamReader(EntryPoint.class.getResourceAsStream("/test.json")) ) {
final Wrapper wrapper = gson.fromJson(reader, Wrapper.class);
out.println(wrapper);
// ... convert the wrapper DTO above to your target business object
}
}
Output:
包装器{id='10',游戏=[PZ],formulaDefinition=FormulaDefinition{count=10,operatorDefinitions=[OperatorDefinition{operators=PLUS,第一个='1-5',第二个='1-5',结果=' 2-5'}]}}
Edit
我在下面的代码片段中对 Gson 的理解是错误的:
@SerializedName("count")
@Expose
private /*final*/ int count /*= 0*/; // Gson works with final primitives like `int` strangely
其实是Gsondoes工作正常。我忘记了 Java 常量内联。得到count
通过反射使用Field
工作完美。但是,由于内联,会返回常量值。类似的普通对象javap -p -c
:
final class ext.Test$Immutable {
private final int foo;
private ext.Test$Immutable();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: aload_0
5: iconst_0
6: putfield #2 // Field foo:I
9: return
private int getFoo();
Code:
0: iconst_0
1: ireturn
public java.lang.String toString();
Code:
0: ldc #4 // String (IMMUTABLE:0)
2: areturn
}
在这种情况下甚至toString()
返回一个常量。是的,这就是 Java 和javac
工作。为了禁用这种内联并添加final
与周围的所有字段类似,应添加一个非编译时值:
@SerializedName("count")
@Expose
private final int count = constOf(0);
where constOf(int)
只是:
private static int constOf(final int value) {
return value;
}
现在可以轻松声明所有传入的 DTO 字段final
.