FastJSON Serialization Mechanism: Why isChinaName() Is Invoked and How to Control Method Inclusion
This article analyzes a FastJSON serialization issue caused by an unexpected call to isChinaName(), explains the underlying JavaBeanSerializer workflow, demonstrates how ASM-generated serializers work, and proposes coding conventions using @JSONField to prevent unwanted method execution during JSON conversion.
Recently a simple feature was added with an extra log line, but after deployment a flood of alerts forced a rollback. The root cause was that FastJSON invoked the isChinaName() method during serialization, where this.country was still null, leading to a NullPointerException.
Incident Review
The added log seemed harmless, yet the production alarm indicated a deeper serialization problem that required immediate investigation.
Scenario Reconstruction
A CountryDTO class was defined with a String country field, standard getters/setters, and an isChinaName() method that checks whether the country equals "中国".
public class CountryDTO {
private String country;
public void setCountry(String country) { this.country = country; }
public String getCountry() { return this.country; }
public Boolean isChinaName() { return this.country.equals("中国"); }
}A test class serializes an instance of CountryDTO using FastJSON:
public class FastJonTest {
@Test
public void testSerialize() {
CountryDTO countryDTO = new CountryDTO();
String str = JSON.toJSONString(countryDTO);
System.out.println(str);
}
}Running the test throws a NullPointerException because FastJSON calls isChinaName() while country is still null.
Source Code Analysis
Debugging shows that FastJSON generates an ASM class named ASMSerializer_1_CountryDTO to avoid reflection overhead. The serializer ultimately delegates to JavaBeanSerializer.write() , which obtains an ObjectWriter via getObjectWriter() . The critical path leads to SerializeConfig#createJavaBeanSerializer and then to TypeUtils#computeGetters .
public static List
computeGetters(Class
clazz, JSONType jsonType,
Map
aliasMap, Map
fieldCacheMap,
boolean sorted, PropertyNamingStrategy propertyNamingStrategy) {
// ... omitted ...
Method[] methods = clazz.getMethods();
for (Method method : methods) {
if (method.getReturnType().equals(Void.TYPE)) continue;
if (method.getParameterTypes().length != 0) continue;
// ... annotation handling ...
if (methodName.startsWith("get")) { /* handle getter */ }
if (methodName.startsWith("is")) { /* handle boolean getter */ }
}
}The algorithm classifies methods into three groups:
@JSONField(serialize = false, name = "xxx") annotations
Methods starting with get
Methods starting with is
Serialization Flowchart
(The original flowchart image is omitted here; it illustrates the steps from object inspection to byte‑code generation and finally to JSON output.)
Example Code
/**
* case1: @JSONField(serialize = false)
* case2: getXxx() returns void
* case3: isXxx() returns non‑boolean
* case4: @JSONType(ignores = "xxx")
*/
@JSONType(ignores = "otherName")
public class CountryDTO {
private String country;
public void setCountry(String country) { this.country = country; }
public String getCountry() { return this.country; }
public static void queryCountryList() { System.out.println("queryCountryList()执行!!"); }
public Boolean isChinaName() { System.out.println("isChinaName()执行!!"); return true; }
public String getEnglishName() { System.out.println("getEnglishName()执行!!"); return "lucy"; }
public String getOtherName() { System.out.println("getOtherName()执行!!"); return "lucy"; }
@JSONField(serialize = false)
public String getEnglishName2() { System.out.println("getEnglishName2()执行!!"); return "lucy"; }
public void getEnglishName3() { System.out.println("getEnglishName3()执行!!"); }
public String isChinaName2() { System.out.println("isChinaName2()执行!!"); return "isChinaName2"; }
}Running the test prints:
isChinaName()执行!!
getEnglishName()执行!!
{"chinaName":true,"englishName":"lucy"}Code Standards
The serialization rules are numerous: return types, parameter counts, and annotations like @JSONType or @JSONField all affect inclusion. To reduce variance among team members, it is recommended to explicitly mark non‑serializable methods with @JSONField(serialize = false) .
public class CountryDTO {
private String country;
public void setCountry(String country) { this.country = country; }
public String getCountry() { return this.country; }
@JSONField(serialize = false)
public static void queryCountryList() { System.out.println("queryCountryList()执行!!"); }
public Boolean isChinaName() { System.out.println("isChinaName()执行!!"); return true; }
public String getEnglishName() { System.out.println("getEnglishName()执行!!"); return "lucy"; }
@JSONField(serialize = false)
public String getOtherName() { System.out.println("getOtherName()执行!!"); return "lucy"; }
@JSONField(serialize = false)
public String getEnglishName2() { System.out.println("getEnglishName2()执行!!"); return "lucy"; }
@JSONField(serialize = false)
public void getEnglishName3() { System.out.println("getEnglishName3()执行!!"); }
@JSONField(serialize = false)
public String isChinaName2() { System.out.println("isChinaName2()执行!!"); return "isChinaName2"; }
}High‑Frequency Serialization Scenarios
(Another diagram shows the three most common cases where serialization triggers unexpected method calls.)
The overall process follows: discover the issue → analyze the principle → solve the problem → elevate to coding standards.
Business side: solve the problem, choose a good solution, and extend it to multiple systems.
Technical side: master the underlying principle of each isolated issue.
Selected Java Interview Questions
A professional Java tech channel sharing common knowledge to help developers fill gaps. Follow us!
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.