类似地添加 final 修饰符到字段中,应该添加非编译时值: @SerializedName(count) @Expose private final int count = constOf( 0); 其中 constOf(int)仅仅是: $ b private static int constOf(final int value){ return value; $ / code> 现在所有传入的DTO字段都可以轻松声明为 final 。 I am implementing a custom JsonDeserializer because of a business logic that is neccessary for processing. But some parts can be parsed standard way. Is this possible - process some elements myself and let some nested elements processed automatically?This is JSON:{ "id": "10", "games": ["PZ"], "definition": { "count": 10, "operatorDefinitions": [ { "operators": "+", "first": "1-5", "second": "1-5", "result": "2-5" } ] }This is custome deserializer for definition item:public class FormulaDefinitionGsonAdapter implements JsonDeserializer<FormulaDefinition> {public FormulaDefinition deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { FormulaDefinition definition = new FormulaDefinition(); JsonObject jsonObject = json.getAsJsonObject(); JsonPrimitive p = jsonObject.getAsJsonPrimitive("count"); definition.setCount(p.getAsInt()); JsonArray array = jsonObject.getAsJsonArray("operatorDefinitions"); if (array == null || array.size() == 0) { throw new JsonParseException("Element 'operatorDefinitions' is missing!"); } for (JsonElement jsonElement : array) { jsonObject = (JsonObject) jsonElement; p = jsonObject.getAsJsonPrimitive("first"); String sValues = p.getAsString(); Values firstArgValues = Values.parse(sValues);And now I would like to let GSON parse operators enum. I can do it myself, it is just few lines of code, but I would prefer library do as much as it can. 解决方案 Well, just use Gson.There is the Data Transfer Object pattern, and Gson mapping classes in particular, that deal with your problem perfectly. By default, if Gson is capable to satisfy mappings by the built-in facilities, and you don't have to do its job yourself except of special cases. Such mapping classes are only meant to exist between JSON content and your business object class in order to (de)serialize the data (simply speaking, DTOs only exist for this purpose, and Gson-related annotations must not spread onto your business classes -- just convert DTOs to business objects).Mappingsfinal 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); }}DeserializationThen, since your operators are meant to be effective enums, this is the only place where you really need a custom JSON deserializer just because Gson default rules are not aware of these rules.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); } }}DemoNow you can just use the Wrapper class to deserialize your JSON:// Gson instances are thread-safe and can be easily instantiated onceprivate 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:EditI was wrong about Gson in the following code snippet: @SerializedName("count") @Expose private /*final*/ int count /*= 0*/; // Gson works with final primitives like `int` strangelyActually Gson does work fine. I forgot Java constants inlining. Getting the count via reflection using Field works perfect. However, the contant values are returned because of inlining. A similar plain object with 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}In this case even toString() returns a constant. Yes, this is how Java and javac work. In order to disable such inlining and add the final modifier to the field like similarly to all fields around, a non-compile-time value should be added: @SerializedName("count") @Expose private final int count = constOf(0);where constOf(int) is merely:private static int constOf(final int value) { return value;}Now all incoming DTO fields can be easily declared final. 这篇关于部分GSON反序列化的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持! 上岸,阿里云!
09-02 10:25