需求:
一个接口入参字段有三个,其中一个字段传入的是base64码值,因为若依框架的操作日志设置了仅保存前2000个字符。导致入参转JsonObject对象时,排序在base64码字段后的字段,在操作日志入参列中丢失。
代码示例
@Data
public class customerParam {
private Integer number;
private String shortStr;
// base64码值字段
private String base64Str;
}
/**
* 获取请求的参数,放到log中
*
* @param operLog 操作日志
* @throws Exception 异常
*/
private void setRequestValue(JoinPoint joinPoint, SysOperLog operLog) throws Exception {
String requestMethod = operLog.getRequestMethod();
if (HttpMethod.PUT.name().equals(requestMethod) || HttpMethod.POST.name().equals(requestMethod))
{
String params = argsArrayToString(joinPoint.getArgs());
// 仅保存参数文本的前2000个字符,base64值远超2000
operLog.setOperParam(StringUtils.substring(params, 0, 2000));
} else {
Map<?, ?> paramsMap = (Map<?, ?>) ServletUtils.getRequest().getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE);
operLog.setOperParam(StringUtils.substring(paramsMap.toString(), 0, 2000));
}
}
起初以为跟传入字段顺序有关,经过调试后发现。在joinPoint.getArgs()时,字段在对象中还是按字段顺序排序的。在经过argsArrayToString方法后,才变为非字段顺序排序。
/**
* 参数拼装
*/
private String argsArrayToString(Object[] paramsArray)
{
String params = "";
if (paramsArray != null && paramsArray.length > 0)
{
for (Object o : paramsArray)
{
if (StringUtils.isNotNull(o) && !isFilterObject(o))
{
try
{
// 经过该行代码,字段顺序错乱,既不符合字母排序也不符合字段排序。
Object jsonObj = JSON.toJSON(o);
params += jsonObj.toString() + " ";
}
catch (Exception e)
{
}
}
}
}
return params.trim();
}
Object o中的字段也依然是按字段顺序排序的,在JSON.toJSON(o)执行后,对象字段改变了。
package com.alibaba.fastjson;
public abstract class JSON implements JSONStreamAware, JSONAware {
/**
* This method serializes the specified object into its equivalent representation as a tree of
* {@link JSONObject}s.
*
*/
public static Object toJSON(Object javaObject) {
return toJSON(javaObject, SerializeConfig.globalInstance);
}
@SuppressWarnings("unchecked")
public static Object toJSON(Object javaObject, SerializeConfig config) {
if (javaObject == null) {
return null;
}
if (javaObject instanceof JSON) {
return javaObject;
}
if (javaObject instanceof Map) {
Map<Object, Object> map = (Map<Object, Object>) javaObject;
int size = map.size();
Map innerMap;
if (map instanceof LinkedHashMap) {
innerMap = new LinkedHashMap(size);
} else if (map instanceof TreeMap) {
innerMap = new TreeMap();
} else {
innerMap = new HashMap(size);
}
JSONObject json = new JSONObject(innerMap);
for (Map.Entry<Object, Object> entry : map.entrySet()) {
Object key = entry.getKey();
String jsonKey = TypeUtils.castToString(key);
Object jsonValue = toJSON(entry.getValue(), config);
json.put(jsonKey, jsonValue);
}
return json;
}
if (javaObject instanceof Collection) {
Collection<Object> collection = (Collection<Object>) javaObject;
JSONArray array = new JSONArray(collection.size());
for (Object item : collection) {
Object jsonValue = toJSON(item, config);
array.add(jsonValue);
}
return array;
}
if (javaObject instanceof JSONSerializable) {
String json = JSON.toJSONString(javaObject);
return JSON.parse(json);
}
Class<?> clazz = javaObject.getClass();
if (clazz.isEnum()) {
return ((Enum<?>) javaObject).name();
}
if (clazz.isArray()) {
int len = Array.getLength(javaObject);
JSONArray array = new JSONArray(len);
for (int i = 0; i < len; ++i) {
Object item = Array.get(javaObject, i);
Object jsonValue = toJSON(item);
array.add(jsonValue);
}
return array;
}
if (ParserConfig.isPrimitive2(clazz)) {
return javaObject;
}
ObjectSerializer serializer = config.getObjectWriter(clazz);
if (serializer instanceof JavaBeanSerializer) {
JavaBeanSerializer javaBeanSerializer = (JavaBeanSerializer) serializer;
JSONType jsonType = javaBeanSerializer.getJSONType();
boolean ordered = false;
if (jsonType != null) {
for (SerializerFeature serializerFeature : jsonType.serialzeFeatures()) {
if (serializerFeature == SerializerFeature.SortField
|| serializerFeature == SerializerFeature.MapSortField) {
ordered = true;
}
}
}
// 当ordered为true时,才会按字段顺序排序。
JSONObject json = new JSONObject(ordered);
try {
Map<String, Object> values = javaBeanSerializer.getFieldValuesMap(javaObject);
for (Map.Entry<String, Object> entry : values.entrySet()) {
json.put(entry.getKey(), toJSON(entry.getValue(), config));
}
} catch (Exception e) {
throw new JSONException("toJSON error", e);
}
return json;
}
String text = JSON.toJSONString(javaObject, config);
return JSON.parse(text);
}
}
new JSONObject() 当传入的参数是false时,创建的是HashMap;当传入的是true时,创建的是LinkedHashMap。
(1)LinkedHashMap保存插入顺序。双向链表维持循序。
(2)HashMap则是无序的。
public JSONObject(boolean ordered){
this(DEFAULT_INITIAL_CAPACITY, ordered);
}
public JSONObject(int initialCapacity, boolean ordered){
if (ordered) {
map = new LinkedHashMap<String, Object>(initialCapacity);
} else {
map = new HashMap<String, Object>(initialCapacity);
}
}
解决方法
使用fastJson的 @JSONField(serialize=false) 注解过滤字段
@Data
public class customerParam {
private Integer number;
private String shortStr;
// base64码值字段
@JSONField(serialize=false)
private String base64Str;
}
在入参字段上添加注解,代表不序列化某些字段。
加上该参数后,Object jsonObj = JSON.toJSON(o)后,仅剩余所需两个字段,但仍是乱序。
思考
1.当参数满足什么条件时,ordered才会是true。
解答:当对象配置@JSONType(serialzeFeatures = SerializerFeature.SortField)或@JSONTypeserializerFeature == SerializerFeature.MapSortField)时
2.为什么使用fastJson排序注解时,JSON.toJSON(o)排序无效。
解答:当实体类配置了类型和排序时有效。@JSONType(serialzeFeatures = SerializerFeature.SortField, orders = {“number”, “shortStr”, “base64Str”})
参考链接
JSONobject排序问题 — 微梦 https://zhuanlan.zhihu.com/p/578409387
指定JSON.toJSONString中实体类属性的输出顺序 — 八十萬空軍縂教頭
https://blog.csdn.net/weixin_42798722/article/details/118228267