如何配置Jackson以忽略Java类中为null或空(empty)的字段

Jackson库提供了@JsonInclude注解和ObjectMapper配置选项,可以用来控制是否在JSON输出中包含null或空值的字段。

默认情况下,Jackson会包含所有字段,不论其值为何。

本教程将展示如何使用Include.NON_NULL来忽略null值字段,以及使用Include.NON_EMPTY来忽略空值字段。

添加依赖项

首先,在你的pom.xml文件中添加以下依赖项:

<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>LATEST_VERSION</version> <!-- 请替换为Maven中央仓库上的最新版本 -->
</dependency>

此依赖会自动引入jackson-corejackson-annotations

@JsonInclude 注解概览

@JsonInclude注解支持两种主要的配置值:

  • Include.NON_NULL: 只有非null的属性会被包含在JSON中。
  • Include.NON_EMPTY: 只有非空(非null且不为空字符串、集合等)的属性会被包含在JSON中。

该注解可以在属性级别或类级别使用,也可以全局配置ObjectMapper实例。

示例代码
1. 属性级别的@JsonInclude配置

我们可以通过在属性上添加@JsonInclude注解来指定个别字段的行为。

package net.javaguides.jackson.annotations;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonInclude.Include;

public class Employee {
    private int id;

    @JsonInclude(Include.NON_NULL)
    private String firstName;

    @JsonInclude(Include.NON_EMPTY)
    private String lastName;

    public Employee(int id, String firstName, String lastName) {
        this.id = id;
        this.firstName = firstName;
        this.lastName = lastName;
    }

    // Getters and setters...
}
2. 类级别的@JsonInclude配置

我们还可以通过在类上添加@JsonInclude注解来为整个类设置统一的行为。

package net.javaguides.jackson.annotations;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonInclude.Include;

@JsonInclude(Include.NON_NULL)
public class Employee {
    private int id;
    private String firstName;

    @JsonInclude(Include.NON_EMPTY)
    private String lastName;

    public Employee(int id, String firstName, String lastName) {
        this.id = id;
        this.firstName = firstName;
        this.lastName = lastName;
    }

    // Getters and setters...
}
3. 使用ObjectMapper全局配置

你也可以在ObjectMapper实例上全局地配置这一行为。请注意,如果你同时设置了Include.NON_NULLInclude.NON_EMPTY,后者将会覆盖前者。

ObjectMapper mapper = new ObjectMapper();
mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
// 或者使用 Include.NON_EMPTY
// mapper.setSerializationInclusion(JsonInclude.Include.NON_EMPTY);
测试代码

这里是一个完整的测试示例,它演示了上述配置的效果:

package net.javaguides.jackson.annotations;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.annotation.JsonInclude.Include;

public class JsonIncludeAnnotationTest {
    public static void main(String[] args) throws JsonProcessingException {
        ObjectMapper mapper = new ObjectMapper();
        mapper.setSerializationInclusion(Include.NON_NULL); // 或者 NON_EMPTY
        mapper.enable(SerializationFeature.INDENT_OUTPUT);

        Employee employee = new Employee(10, null, "");
        String result = mapper.writeValueAsString(employee);

        System.out.println(result);
    }
}

运行上述代码后,如果firstNamenulllastName是空字符串,那么最终的JSON输出将只包含id字段。

注意:在最新的Jackson版本中,setSerializationInclusion方法已经被弃用,推荐使用setDefaultInclusionwriterWithDefaultPrettyPrinter().with(...). 例如:

ObjectMapper mapper = new ObjectMapper();
mapper.setDefaultInclusion(JsonInclude.Value.construct(Include.NON_NULL, Include.NON_EMPTY));

这将确保你的项目遵循最佳实践,并与未来的Jackson版本兼容。

package com.jxyunge.sign.aspect; import com.baomidou.mybatisplus.core.toolkit.StringUtils; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.NullNode; import com.jxyunge.sign.enums.sys.OperationTypeEnum; import com.jxyunge.sign.mybatis.entity.sys.Admin; import com.jxyunge.sign.mybatis.entity.sys.SysLog; import com.jxyunge.sign.mybatis.service.sys.AdminService; import com.jxyunge.sign.mybatis.service.sys.EntityService; import com.jxyunge.sign.mybatis.service.sys.SysLogService; import com.jxyunge.sign.request.input.common.PageParam; import com.jxyunge.sign.tools.IpHelper; import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.reflect.MethodSignature; import org.springframework.stereotype.Component; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.*; import java.util.concurrent.CompletableFuture; import java.util.stream.Collectors; /** * @author lgh */ @Slf4j @Aspect @AllArgsConstructor @Component public class SysLogAspect { private final AdminService adminService; private final SysLogService sysLogService; private final EntityService entityService; private ObjectMapper objectMapper; @Around("@annotation(sysLogAnnotation)") public Object logAround(ProceedingJoinPoint joinPoint, com.jxyunge.sign.annotation.SysLog sysLogAnnotation) throws Throwable { log.info("开始执行日志记录"); long beginTime = System.currentTimeMillis(); OperationTypeEnum operationType = sysLogAnnotation.type(); // 获取旧数据 Object oldEntity = getOldEntity(joinPoint, sysLogAnnotation); // 执行目标方法 Object result = joinPoint.proceed(); long time = System.currentTimeMillis() - beginTime; // 获取新数据 Object newEntity = extractEntityFromResponse(result); // 构建日志记录 SysLog sysLog = buildSysLog(joinPoint, sysLogAnnotation, time, oldEntity, newEntity, operationType); // 异步保存日志 CompletableFuture.runAsync(() -> sysLogService.save(sysLog)); return result; } private Object getOldEntity(ProceedingJoinPoint joinPoint, com.jxyunge.sign.annotation.SysLog annotation) { if (annotation.type() == OperationTypeEnum.CREATE) return null; try { Object idValue = getArgValue(joinPoint, annotation.idParam()); if (idValue == null) return null; // 必须指定 entityClass,否则无法获取完整实体 if (annotation.entityClass() == null || annotation.entityClass() == Object.class) { log.warn("SysLog 注解中未指定 entityClass,无法获取旧实体数据"); return null; } return entityService.getById(annotation.entityClass(), idValue.toString()); } catch (Exception e) { log.error("获取旧实体失败", e); return null; } } private Object getArgValue(ProceedingJoinPoint joinPoint, String paramName) { if (StringUtils.isBlank(paramName)) return null; MethodSignature signature = (MethodSignature) joinPoint.getSignature(); String[] paramNames = signature.getParameterNames(); Object[] args = joinPoint.getArgs(); // 支持嵌套属性,如"entity.id" if (paramName.contains(".")) { String[] parts = paramName.split("\\."); String rootParam = parts[0]; Object rootObj = null; for (int i = 0; i < paramNames.length; i++) { if (rootParam.equals(paramNames[i])) { rootObj = args[i]; break; } } if (rootObj == null) return null; // 递归获取嵌套属性 try { for (int i = 1; i < parts.length; i++) { String fieldName = parts[i]; Field field = findField(rootObj.getClass(), fieldName); if (field == null) { log.warn("字段 {} 在 {} 及其父中未找到", fieldName, rootObj.getClass().getName()); rootObj = null; break; } field.setAccessible(true); rootObj = field.get(rootObj); if (rootObj == null) break; } return rootObj; } catch (Exception e) { log.error("获取嵌套参数失败: {}", paramName, e); return null; } } else { // 原有逻辑,直接匹配参数名 for (int i = 0; i < paramNames.length; i++) { if (paramName.equals(paramNames[i])) { return args[i]; } } return null; } } private Field findField(Class<?> clazz, String fieldName) { while (clazz != null) { try { return clazz.getDeclaredField(fieldName); } catch (NoSuchFieldException e) { clazz = clazz.getSuperclass(); } } return null; } private Object extractEntityFromResponse(Object result) { if (result == null) return null; // 1. 尝试从通用响应结构获取 try { Method getData = result.getClass().getMethod("getData"); Object data = getData.invoke(result); if (data != null) return data; } catch (NoSuchMethodException ignored) { } catch (Exception e) { log.warn("getData调用异常", e); } // 2. 尝试直接访问data字段 try { Field dataField = result.getClass().getDeclaredField("data"); dataField.setAccessible(true); Object data = dataField.get(result); if (data != null) return data; } catch (NoSuchFieldException ignored) { } catch (Exception e) { log.warn("data字段访问异常", e); } // 3. 返回原始结果 return result; } private SysLog buildSysLog(ProceedingJoinPoint joinPoint, com.jxyunge.sign.annotation.SysLog annotation, long time, Object oldEntity, Object newEntity, OperationTypeEnum operationType) { SysLog sysLog = new SysLog(); // 1. 设置基础字段(原有逻辑) // 注解描述 sysLog.setOperation(annotation.value()); // 请求方法名 String className = joinPoint.getTarget().getClass().getName(); String methodName = joinPoint.getSignature().getName(); sysLog.setMethod(className + "." + methodName + "()"); // 请求参数 Object[] args = joinPoint.getArgs(); if(args != null && args.length > 0) { String params = null; try { if(args.length > 0 && args[0] != null && Objects.equals(args[0].getClass().getName(), PageParam.class.getName())){ PageParam<Object> pageParam = (PageParam<Object>) args[0]; List<Object> records = pageParam.getRecords(); List<Object> filteredRecords = filterSerializableObjects(records); params = objectMapper.writeValueAsString(filteredRecords); } else { Object[] filteredArgs = filterSerializableObjects(args); params = objectMapper.writeValueAsString(filteredArgs); } } catch (Exception e) { params = getSafeParamRepresentation(args); log.warn("参数序列化失败: {}", e.getMessage()); } sysLog.setParams(params); } // IP地址 sysLog.setIp(IpHelper.getIpAddr()); // 业务ID(从新实体中提取) String businessId = extractIdFromResult(newEntity != null ? newEntity : oldEntity); if (businessId != null) { sysLog.setBizId(businessId); } // 用户名 Admin admin = adminService.getAdminFromReq(); if (!Objects.isNull(admin)) { sysLog.setUsername(admin.getUsername()); } // 执行时间和创建时间 sysLog.setTime(time); sysLog.setCreateDate(new Date()); // 2. 新增操作日志字段 sysLog.setOperationType(OperationTypeEnum.valueOf(operationType.name())); sysLog.setBeforeData(toJsonSafe(oldEntity)); sysLog.setAfterData(toJsonSafe(newEntity)); sysLog.setChangeDetails(compareChanges(oldEntity, newEntity)); return sysLog; } private String toJsonSafe(Object obj) { if (obj == null) return null; try { return objectMapper.writeValueAsString(obj); } catch (JsonProcessingException e) { return "序列化失败: " + e.getMessage(); } } private String compareChanges(Object oldObj, Object newObj) { if (oldObj == null && newObj == null) return "[]"; try { JsonNode oldTree = oldObj != null ? objectMapper.valueToTree(oldObj) : objectMapper.createObjectNode(); JsonNode newTree = newObj != null ? objectMapper.valueToTree(newObj) : objectMapper.createObjectNode(); List<Map<String, Object>> changes = new ArrayList<>(); Set<String> allFields = new HashSet<>(); oldTree.fieldNames().forEachRemaining(allFields::add); newTree.fieldNames().forEachRemaining(allFields::add); for (String field : allFields) { JsonNode oldVal = oldTree.get(field); JsonNode newVal = newTree.get(field); if (oldVal == null) oldVal = NullNode.getInstance(); if (newVal == null) newVal = NullNode.getInstance(); if (!oldVal.equals(newVal)) { // 使用 HashMap 替代 Map.of,避免 null 值导致的 NullPointerException Map<String, Object> change = new HashMap<>(); change.put("field", field != null ? field : "null"); // 处理可能的 null 值 String oldText = oldVal.isNull() ? null : (oldVal.isTextual() ? oldVal.asText() : oldVal.toString()); String newText = newVal.isNull() ? null : (newVal.isTextual() ? newVal.asText() : newVal.toString()); change.put("oldValue", oldText); change.put("newValue", newText); changes.add(change); } } return objectMapper.writeValueAsString(changes); } catch (Exception e) { log.error("比较变更详情时发生异常: {}", e.getMessage(), e); // 更详细的异常记录 return String.format("[{\"error\":\"%s\"}]", e.getMessage()); } } private String extractIdFromResult(Object result) { if (result == null) return null; // 尝试从常见ID字段获取 String[] idFieldNames = {"id", "Id", "ID", "uuid", "key"}; for (String fieldName : idFieldNames) { try { Field field = result.getClass().getDeclaredField(fieldName); field.setAccessible(true); Object value = field.get(result); if (value != null) return value.toString(); } catch (NoSuchFieldException ignored) { } catch (Exception e) { log.warn("ID字段[{}]访问异常", fieldName, e); } } // 尝试通过Getter方法获取 try { Method getId = result.getClass().getMethod("getId"); Object idValue = getId.invoke(result); if (idValue != null) return idValue.toString(); } catch (NoSuchMethodException ignored) { } catch (Exception e) { log.warn("getId方法调用异常", e); } return null; } // 过滤可序列化对象的方法 private List<Object> filterSerializableObjects(List<Object> objects) { if (objects == null) return new ArrayList<>(); return objects.stream() .filter(this::isSerializable) .collect(Collectors.toList()); } private Object[] filterSerializableObjects(Object[] objects) { if (objects == null) return new Object[0]; return Arrays.stream(objects) .filter(this::isSerializable) .toArray(); } // 检查对象是否可序列化 private boolean isSerializable(Object obj) { if (obj == null) return true; try { // 尝试快速判断是否为常见不可序列化型 Class<?> clazz = obj.getClass(); String className = clazz.getName(); // 过滤常见的不可序列化 if (className.contains("java.util.Collections$") || className.contains("HttpServletRequest") || className.contains("HttpServletResponse") || className.contains("Session") || className.contains("ServletContext") || className.startsWith("org.apache.catalina") || className.startsWith("org.springframework.security")) { return false; } // 简单测试序列化 objectMapper.writeValueAsString(obj); return true; } catch (Exception e) { return false; } } // 获取安全的参数表示 private String getSafeParamRepresentation(Object[] args) { if (args == null || args.length == 0) { return "[]"; } List<String> paramInfos = new ArrayList<>(); for (int i = 0; i < args.length; i++) { if (args[i] != null) { String className = args[i].getClass().getSimpleName(); paramInfos.add("arg" + i + ": " + className); } else { paramInfos.add("arg" + i + ": null"); } } try { return objectMapper.writeValueAsString(paramInfos); } catch (Exception e) { return paramInfos.toString(); } } } package com.jxyunge.sign.annotation; import com.jxyunge.sign.enums.sys.OperationTypeEnum; import java.lang.annotation.*; /** * 自定义日志注解 * @author linyan */ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface SysLog { String value() default ""; OperationTypeEnum type() default OperationTypeEnum.UPDATE; // 操作型 // 新增ID参数名(用于获取旧数据) String idParam() default ""; // 新增实体参数 Class<?> entityClass() default Object.class; } package com.jxyunge.sign.mybatis.entity.sys; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import com.jxyunge.sign.annotation.SearchParam; import com.jxyunge.sign.enums.sys.OperationTypeEnum; import com.jxyunge.sign.enums.sys.SearchConditionEnum; import com.jxyunge.sign.mybatis.entity.BaseEntity; import lombok.Data; import java.io.Serializable; import java.util.Date; import java.util.List; /** * 系统日志 * @author zanmall */ @Data @TableName("tb_sys_log") public class SysLog implements Serializable { private static final long serialVersionUID = 1L; /** * 主键 */ @SearchParam(name = {"主键"},condition = {SearchConditionEnum.eq}) @TableId(type = IdType.ASSIGN_UUID) private String id; /** * 用户名 */ private String username; /** * 用户操作 */ private String operation; /** * 请求方法 */ private String method; /** * 请求参数 */ private String params; /** * 执行时长(毫秒) */ private Long time; /** * IP地址 */ private String ip; /** * 创建时间 */ private Date createDate; /** * 业务id */ private String bizId; // 新增字段 private OperationTypeEnum operationType; // 操作型 private String beforeData; // 变更前数据(JSON) private String afterData; // 变更后数据(JSON) private String changeDetails; // 变更详情(JSON) } // 文件路径: D:\develop-tools\code\ideaworkplace\zhongbang-scm\zhongbang-scm-service\src\main\java\com\jxyunge\sign\mybatis\service\impl\sys\EntityServiceImpl.java package com.jxyunge.sign.mybatis.service.impl.sys; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.jxyunge.sign.mybatis.entity.scm.sys.Order; import com.jxyunge.sign.mybatis.service.sys.EntityService; import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.stereotype.Service; import java.io.Serializable; import java.util.HashMap; import java.util.Map; /** * @author xiaoda * @create 2025-07-30 10:38 */ @Service public class EntityServiceImpl implements EntityService, ApplicationContextAware { private ApplicationContext applicationContext; @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.applicationContext = applicationContext; } @Override public <T> T getById(Class<T> entityClass, Serializable id) { try { // 尝试多种方式获取 Mapper BaseMapper<T> mapper = findMapperForEntity(entityClass); if (mapper != null) { return mapper.selectById(id); } throw new RuntimeException("未找到处理实体 " + entityClass.getSimpleName() + " 的 Mapper"); } catch (Exception e) { throw new RuntimeException("获取实体失败: " + entityClass.getSimpleName() + ", ID: " + id, e); } } @Override public Object getById(String id) { // 对于没有前缀的普通ID,我们无法自动识别实体型 // 这种情况应该始终通过带 entityClass 参数的 getById 方法来调用 throw new UnsupportedOperationException("无法通过普通ID自动识别实体型,请使用 getById(Class<T> entityClass, Serializable id) 方法"); } /** * 查找处理指定实体的 Mapper */ private <T> BaseMapper<T> findMapperForEntity(Class<T> entityClass) { // 尝试多种可能的命名方式,按照项目实际命名规则排序 String[] possibleNames = getPossibleMapperNames(entityClass); for (String mapperBeanName : possibleNames) { if (applicationContext.containsBean(mapperBeanName)) { try { return applicationContext.getBean(mapperBeanName, BaseMapper.class); } catch (Exception ignored) { // 如果获取失败,继续尝试下一个名称 } } } return null; } /** * 获取可能的 Mapper Bean 名称(按照项目实际命名规则排序) */ private String[] getPossibleMapperNames(Class<?> entityClass) { String simpleName = entityClass.getSimpleName(); return new String[] { "sys" + simpleName + "Mapper", // sys前缀: sysOrderMapper, sysGoodsMapper Character.toLowerCase(simpleName.charAt(0)) + simpleName.substring(1) + "Mapper", // 首字母小写: orderMapper, goodsMapper simpleName.toLowerCase() + "Mapper", // 全小写: ordermapper, goodsmapper "tb" + simpleName + "Mapper", // tb前缀: tbOrderMapper, tbGoodsMapper simpleName + "Mapper" // 原样: OrderMapper, GoodsMapper }; } // 简单实体(如果需要的话,但建议移除) private static class SimpleEntity { private String id; public SimpleEntity(String id) { this.id = id; } public String getId() { return id; } public void setId(String id) { this.id = id; } } } @SysLog(value = "修改订单", type = OperationTypeEnum.UPDATE,idParam = "entity.id",entityClass = Order.class) @PostMapping(value = "/updateOrder") @Operation(summary = "修改订单") public ApiOutput<OrderAndDetailsOutput> updateOrder(HttpServletRequest request, @RequestBody OrderAddInput entity) { Admin admin = getAdminFromReq(request); if (BeanUtil.isEmpty(entity)){ return ApiOutput.err(ErrorCodeEnum.PARAMETER_IS_EMPTY); } OrderAndDetailsOutput save = sysOrderService.updateOrder(entity,admin); return ApiOutput.ok(save); } 这些是我的操作日志实现相关的代码,可以查看新增,删除,编辑记录,点击详情可查看明细(查看变更字段的变化),请你帮我分析一下,这些代码是否能实现这些功能,下面的数据是我在测试修改订单时数据库中的数据,看看有什么问题 修改日志的before_data里面的数据是:{"id":"ORD4103888","createTime":"2025-07-30 13:37:04","modifyTime":"2025-07-30 17:03:38","merchantId":"83210837","operationTimeId":"83953091","deliveryStartTime":"2025-07-24T06:00:27.000+00:00","deliveryEndTime":"2025-07-24T06:00:27.000+00:00","status":0,"payStatus":0,"outStatus":0,"payType":0,"type":0,"remark":"","deliveryMethod":0,"total":180.00,"receiver":"达","receiverPhone":"78945632112","receiverAddress":"","lastOperation":"","signMethod":"0","sortingTaskId":"","source":0,"printCount":0,"printTime":null,"receiveTime":null,"creatorId":"1922052782395863042","loadingStatus":0,"pickupPointId":null,"refundStatus":0,"dateType":0,"dateStart":null,"dateEnd":null,"searchType":0,"search":null,"geotags":null,"lineId":null,"quotationId":null,"driverId":null,"printed":null,"hasRemark":null,"isCurrentPrice":null,"creator":null,"operatorId":"1922052782395863042","operator":null,"merchantName":null,"itemCount":null,"storeCode":null,"accountId":null,"account":null,"merchantLabel":null,"settlementMethod":null,"driverName":null,"packageStatus":0} 而after_data里面的数据是:,{"id":"ORD4103888","createTime":"2025-07-30 13:37:04","modifyTime":"2025-07-30 17:10:32","merchantId":"83210837","operationTimeId":"83953091","deliveryStartTime":"2025-07-24T06:00:01.861+00:00","deliveryEndTime":"2025-07-24T06:00:01.861+00:00","status":0,"payStatus":0,"outStatus":0,"payType":0,"type":0,"remark":"","deliveryMethod":0,"total":180,"receiver":"达","receiverPhone":"78945632112","receiverAddress":"","lastOperation":"","signMethod":"0","sortingTaskId":"","source":0,"printCount":0,"printTime":null,"receiveTime":null,"creatorId":"1922052782395863042","loadingStatus":0,"pickupPointId":null,"refundStatus":0,"dateType":0,"dateStart":null,"dateEnd":null,"searchType":0,"search":null,"geotags":null,"lineId":null,"quotationId":null,"driverId":null,"printed":null,"hasRemark":null,"isCurrentPrice":null,"creator":null,"operatorId":"1922052782395863042","operator":null,"merchantName":null,"itemCount":null,"storeCode":null,"accountId":null,"account":null,"merchantLabel":null,"settlementMethod":null,"driverName":null,"orderDetailsList":[{"id":"84158442","createTime":"2025-07-30 13:44:12","modifyTime":"2025-07-3017:10:32","orderId":"ORD4103888","goodsId":"83222762","goodsSpecificationId":"83271360","quotationId":"83257135","number":9.00,"basicNumber":18,"salesUnit":"斤","categoryId":"1940302497016995842","basicUnit":"斤","price":20.00,"basicPrice":null,"remark":"","amount":180.00,"goodsName":"排骨","specificationName":null,"merchantName":null,"quotationName":"测试报价单01","image":null,"goodsSpecificationName":"排骨","salesUnitQuantity":1.00,"classificationName":"肉/猪肉/排骨"}],"packageStatus":0} change_details里面的数据是:[{"newValue":"2025-07-30 17:10:32","field":"modifyTime","oldValue":"2025-07-30 17:03:38"},{"newValue":"2025-07-24T06:00:01.861+00:00","field":"deliveryEndTime","oldValue":"2025-07-24T06:00:27.000+00:00"},{"newValue":"2025-07-24T06:00:01.861+00:00","field":"deliveryStartTime","oldValue":"2025-07-24T06:00:27.000+00:00"},{"newValue":"[{\"id\":\"84158442\",\"createTime\":\"2025-07-30 13:44:12\",\"modifyTime\":\"2025-07-30 17:10:32\",\"orderId\":\"ORD4103888\",\"goodsId\":\"83222762\",\"goodsSpecificationId\":\"83271360\",\"quotationId\":\"83257135\",\"number\":9,\"basicNumber\":18,\"salesUnit\":\"斤\",\"categoryId\":\"1940302497016995842\",\"basicUnit\":\"斤\",\"price\":2E+1,\"basicPrice\":null,\"remark\":\"\",\"amount\":1.8E+2,\"goodsName\":\"排骨\",\"specificationName\":null,\"merchantName\":null,\"quotationName\":\"测试报价单01\",\"image\":null,\"goodsSpecificationName\":\"排骨\",\"salesUnitQuantity\":1,\"classificationName\":\"肉/猪肉/排骨\"}]","field":"orderDetailsList","oldValue":null}]
07-31
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值