PropertyNamingStrategy
There are four serialization methods.
CamelCase strategy, Java object attribute: personId, attribute after serialization: personId – actually only the first letter is changed from uppercase to lowercase
PascalCase strategy, Java object attribute: personId, attribute after serialization: PersonId – actually only the first letter is changed from lowercase to uppercase
SnakeCase strategy, Java object attribute: personId, attribute after serialization: person_id -- add an underscore before the uppercase letter
KebabCase strategy, Java object attribute: personId, attribute after serialization: person-id - plus and minus signs before uppercase letters
public enum PropertyNamingStrategy { CamelCase, //hump PascalCase, // SnakeCase, //Underscore before capital letters KebabCase; public String translate(String propertyName) { switch (this) { case SnakeCase: { StringBuilder buf = new StringBuilder(); for (int i = 0; i < propertyName.length(); ++i) { char ch = propertyName.charAt(i); if (ch >= 'A' && ch <= 'Z') { char ch_ucase = (char) (ch + 32); if (i > 0) { buf.append('_'); } buf.append(ch_ucase); } else { buf.append(ch); } } return buf.toString(); } case KebabCase: { StringBuilder buf = new StringBuilder(); for (int i = 0; i < propertyName.length(); ++i) { char ch = propertyName.charAt(i); if (ch >= 'A' && ch <= 'Z') { char ch_ucase = (char) (ch + 32); if (i > 0) { buf.append('-'); } buf.append(ch_ucase); } else { buf.append(ch); } } return buf.toString(); } case PascalCase: { char ch = propertyName.charAt(0); if (ch >= 'a' && ch <= 'z') { char[] chars = propertyName.toCharArray(); chars[0] -= 32; return new String(chars); } return propertyName; } case CamelCase: { char ch = propertyName.charAt(0); if (ch >= 'A' && ch <= 'Z') { char[] chars = propertyName.toCharArray(); chars[0] += 32; return new String(chars); } return propertyName; } default: return propertyName; } }
What works is the translate method
Specify the serialization format
After understanding PropertyNamingStrategy, see how it works,
Read the source code and find that when buildingBeanInfo (note that when converting bean s to json and building json information, if it is a map, JSONObject will not have this conversion)
if(propertyNamingStrategy != null && !fieldAnnotationAndNameExists){ propertyName = propertyNamingStrategy.translate(propertyName); }
Here call the method corresponding to PropertyNamingStrategy respectively
Common Misunderstandings
That is to say, setting the output format through PropertyNamingStrategy is only valid for javaBean, and, as for the conversion result, it needs to be analyzed according to the content of the PropertyNamingStrategy#translate method
If the fields in the javaBean are separated by underscores, then specifying CamelCase for serialization cannot be converted to camel case!
E.g
Student student = new Student(); student.setTest_name("test"); SerializeConfig serializeConfig = new SerializeConfig(); serializeConfig.setPropertyNamingStrategy(PropertyNamingStrategy.CamelCase); System.out.println(JSON.toJSONString(student,serializeConfig));
Output {test_name": "test"}, because the CamelCase of PropertyNamingStrategy#translate is executed, just to judge if the first letter is converted from uppercase to lowercase. It cannot be completed, the conversion from underscore to camelcase
case CamelCase: { char ch = propertyName.charAt(0); if (ch >= 'A' && ch <= 'Z') { char[] chars = propertyName.toCharArray(); chars[0] += 32; return new String(chars); } return propertyName; }
Specify the deserialization format
Smart matching function
When fastjson deserializes, it can automatically convert underscore to camel case. This is very convenient. , no matter which form is used during deserialization, it can match successfully and set the value
String str = "{'user_name':123}"; User user = JSON.parseObject(str, User.class); System.out.println(user);
output {userName='123'}
fastjson intelligent matching process
When fastjson is deserializing, when parsing the key value of each json field, it will call
com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer#parseField
this way
Taking the above example as an example, set a breakpoint through debug to see the processing logic when parsing user_id.
At this time, the key in this method is user_id, and the object is the result object to be deserialized. In this example, it is FastJsonTestMain.UserInfo
public boolean parseField(DefaultJSONParser parser, String key, Object object, Type objectType, Map<String, Object> fieldValues, int[] setFlags) { JSONLexer lexer = parser.lexer; // xxx //Whether to disable smart matching; final int disableFieldSmartMatchMask = Feature.DisableFieldSmartMatch.mask; final int initStringFieldAsEmpty = Feature.InitStringFieldAsEmpty.mask; FieldDeserializer fieldDeserializer; if (lexer.isEnabled(disableFieldSmartMatchMask) || (this.beanInfo.parserFeatures & disableFieldSmartMatchMask) != 0) { fieldDeserializer = getFieldDeserializer(key); } else if (lexer.isEnabled(initStringFieldAsEmpty) || (this.beanInfo.parserFeatures & initStringFieldAsEmpty) != 0) { fieldDeserializer = smartMatch(key); } else { //Do smart matching fieldDeserializer = smartMatch(key, setFlags); } ***omitted here N Multi-line*** }
Look at the core code again, intelligent matching smartMatch
public FieldDeserializer smartMatch(String key, int[] setFlags) { if (key == null) { return null; } FieldDeserializer fieldDeserializer = getFieldDeserializer(key, setFlags); if (fieldDeserializer == null) { if (this.smartMatchHashArray == null) { long[] hashArray = new long[sortedFieldDeserializers.length]; for (int i = 0; i < sortedFieldDeserializers.length; i++) { //The nameHashCode of the java field, see the source code below hashArray[i] = sortedFieldDeserializers[i].fieldInfo.nameHashCode; } //Get the field name hashcode value of the deserialized target object and sort it Arrays.sort(hashArray); this.smartMatchHashArray = hashArray; } // smartMatchHashArrayMapping long smartKeyHash = TypeUtils.fnv1a_64_lower(key); //Perform binary search to determine whether to find int pos = Arrays.binarySearch(smartMatchHashArray, smartKeyHash); if (pos < 0) { //The original field is not matched, use fnv1a_64_extract to process it and match again long smartKeyHash1 = TypeUtils.fnv1a_64_extract(key); pos = Arrays.binarySearch(smartMatchHashArray, smartKeyHash1); } boolean is = false; if (pos < 0 && (is = key.startsWith("is"))) { //After the above operation, there is still no match, remove is and match again smartKeyHash = TypeUtils.fnv1a_64_extract(key.substring(2)); pos = Arrays.binarySearch(smartMatchHashArray, smartKeyHash); } if (pos >= 0) { //Successfully matched through the smart matching field if (smartMatchHashArrayMapping == null) { short[] mapping = new short[smartMatchHashArray.length]; Arrays.fill(mapping, (short) -1); for (int i = 0; i < sortedFieldDeserializers.length; i++) { int p = Arrays.binarySearch(smartMatchHashArray, sortedFieldDeserializers[i].fieldInfo.nameHashCode); if (p >= 0) { mapping[p] = (short) i; } } smartMatchHashArrayMapping = mapping; } int deserIndex = smartMatchHashArrayMapping[pos]; if (deserIndex != -1) { if (!isSetFlag(deserIndex, setFlags)) { fieldDeserializer = sortedFieldDeserializers[deserIndex]; } } } if (fieldDeserializer != null) { FieldInfo fieldInfo = fieldDeserializer.fieldInfo; if ((fieldInfo.parserFeatures & Feature.DisableFieldSmartMatch.mask) != 0) { return null; } Class fieldClass = fieldInfo.fieldClass; if (is && (fieldClass != boolean.class && fieldClass != Boolean.class)) { fieldDeserializer = null; } } } return fieldDeserializer; }
It can be seen from the above smartMatch method that the reason why the underscore can be automatically converted to hump in fastjson is mainly because the fnv1a_64_lower and fnv1a_64_extract methods are used for field comparison.
fnv1a_64_extract method source code:
public static long fnv1a_64_extract(String key) { long hashCode = fnv1a_64_magic_hashcode; for (int i = 0; i < key.length(); ++i) { char ch = key.charAt(i); //Remove underscores and minus signs if (ch == '_' || ch == '-') { continue; } //uppercase to lowercase if (ch >= 'A' && ch <= 'Z') { ch = (char) (ch + 32); } hashCode ^= ch; hashCode *= fnv1a_64_magic_prime; } return hashCode; }
As can be seen from the source code, the fnv1a_64_extract method mainly does this:
Remove underscores, minus signs, and convert uppercase to lowercase
Summarize
The principle of intelligent field matching in fastjson is to use the TypeUtils.fnv1a_64_lower method to convert all fields to lowercase during field matching.
Then use the TypeUtils.fnv1a_64_extract method to remove the "_" and "-" symbols from the json field, and then convert all of them to lowercase.
If the above operation still fails to match successfully, it will perform another match by removing the is in the json field.
If the above operation still fails to match successfully, it will perform another match by removing the is in the json field.
When Smart Matching is turned off
It is turned on by default during smart matching, and needs to be turned off manually, see this example
String str = "{'user_name':123}"; ParserConfig parserConfig = new ParserConfig(); parserConfig.propertyNamingStrategy = PropertyNamingStrategy.SnakeCase; User user = JSON.parseObject(str, User.class, parserConfig,Feature.DisableFieldSmartMatch); System.out.println(user);
output {userName='null'}
So how to complete the conversion from underscore to camel case in this case
Then you need to use parseConfig
String str = "{'user_name':123}"; ParserConfig parserConfig = new ParserConfig(); parserConfig.propertyNamingStrategy = PropertyNamingStrategy.SnakeCase; User user = JSON.parseObject(str, User.class,parserConfig,Feature.DisableFieldSmartMatch); System.out.println(user);
So how does PropertyNamingStrategy.SnakeCase work at this time?
Breakpoint PropertyNamingStrategy#translate method
Found that when building the JavaBeanDeserializer
public JavaBeanDeserializer(ParserConfig config, Class<?> clazz, Type type){ this(config // , JavaBeanInfo.build(clazz, type, config.propertyNamingStrategy, config.fieldBased, config.compatibleWithJavaBean, config.isJacksonCompatible()) ); }
if (propertyNamingStrategy != null) { propertyName = propertyNamingStrategy.translate(propertyName); } add(fieldList, new FieldInfo(propertyName, method, field, clazz, type, ordinal, serialzeFeatures, parserFeatures, annotation, fieldAnnotation, null, genericInfo));
The propertyName will be translate d according to the configuration. The attribute name converted to the corresponding format
Common mistakes:
The same as the serialization misunderstanding, if it is a map, JSONObject will not have this conversion, and the conversion result needs to be viewed by referring to the logic of the translate method
It is worth noting that the toJavaObject method of JSONObject, smart matching will take effect. You can safely convert between underscore and camel case
String str = "{'user_name':123}"; JSONObject object = (JSONObject) JSON.parse(str); System.out.println(object); User user = object.toJavaObject(User.class); System.out.println(user);