1. 为什么需要JSON压缩工具类?
JSON作为现代应用中最常用的数据交换格式,其体积膨胀问题在实际开发中越来越突出。我曾在电商项目中遇到过单个商品详情接口返回的JSON数据超过500KB的情况,这在移动端网络环境下简直是灾难性的体验。
JSON压缩的核心价值在于:
- 减少网络传输量:压缩后的JSON能显著降低带宽消耗
- 提升解析效率:更小的数据量意味着更快的序列化/反序列化速度
- 节省存储空间:对于需要持久化的JSON数据尤为明显
2. 核心压缩方案设计
2.1 键名缩短策略
最有效的压缩手段是对JSON键名进行替换。原始JSON中冗长的键名如"customerShippingAddress"可以替换为"csa"。我们采用双向映射字典来实现可逆压缩:
java复制public class KeyDictionary {
private static final Map<String, String> COMPRESS_MAP = new ConcurrentHashMap<>();
private static final Map<String, String> DECOMPRESS_MAP = new ConcurrentHashMap<>();
static {
// 预置常用键名映射
registerMapping("timestamp", "ts");
registerMapping("userInformation", "ui");
// 可动态添加新映射
}
public static void registerMapping(String original, String compressed) {
COMPRESS_MAP.put(original, compressed);
DECOMPRESS_MAP.put(compressed, original);
}
}
注意:键名映射需要保证全局唯一性,建议在应用启动时统一注册
2.2 数值优化处理
JSON中的数值类型经常存在优化空间:
- 大整数转换为Base64编码
- 浮点数精度控制(保留3位小数通常足够)
- 科学计数法转换
java复制public class NumberCompressor {
public static String compressNumber(Number value) {
if(value instanceof Double || value instanceof Float) {
return String.format("%.3f", value.doubleValue())
.replaceAll("0+$", "").replaceAll("\\.$", "");
}
if(value.longValue() > 1000000) {
return "b64:" + Base64.getEncoder()
.encodeToString(BigInteger.valueOf(value.longValue()).toByteArray());
}
return value.toString();
}
}
2.3 空白字符处理
标准的JSON格式化输出包含大量空白字符。我们提供两种处理模式:
- 完全去除(生产环境推荐)
- 智能缩进(开发调试时使用)
java复制public class WhitespaceUtils {
private static final Pattern WHITESPACE = Pattern.compile("\\s+");
public static String removeAll(String json) {
return WHITESPACE.matcher(json).replaceAll("");
}
public static String smartIndent(String json) {
// 实现智能缩进算法...
}
}
3. 完整工具类实现
3.1 压缩处理器
java复制public class JsonCompressor {
private static final ObjectMapper mapper = new ObjectMapper();
public static String compress(String json) throws IOException {
JsonNode root = mapper.readTree(json);
compressNode(root);
return mapper.writeValueAsString(root);
}
private static void compressNode(JsonNode node) {
if(node.isObject()) {
ObjectNode obj = (ObjectNode)node;
Iterator<Map.Entry<String, JsonNode>> fields = obj.fields();
List<Map.Entry<String, JsonNode>> entries = new ArrayList<>();
fields.forEachRemaining(entries::add);
for(Map.Entry<String, JsonNode> entry : entries) {
String compressedKey = KeyDictionary.compress(entry.getKey());
obj.set(compressedKey, entry.getValue());
obj.remove(entry.getKey());
compressNode(entry.getValue());
}
}
// 处理数组和值类型...
}
}
3.2 解压处理器
java复制public class JsonDecompressor {
public static String decompress(String compressedJson) throws IOException {
JsonNode root = mapper.readTree(compressedJson);
decompressNode(root);
return mapper.writerWithDefaultPrettyPrinter()
.writeValueAsString(root);
}
private static void decompressNode(JsonNode node) {
// 逆向处理逻辑...
}
}
4. 性能优化技巧
4.1 对象池技术
频繁创建JSON解析器实例会导致性能问题。我们使用对象池复用ObjectMapper:
java复制public class MapperPool {
private static final int MAX_POOL_SIZE = 10;
private static final LinkedBlockingQueue<ObjectMapper> pool =
new LinkedBlockingQueue<>(MAX_POOL_SIZE);
static {
for(int i=0; i<MAX_POOL_SIZE; i++) {
pool.offer(createConfiguredMapper());
}
}
public static ObjectMapper borrowMapper() throws InterruptedException {
return pool.take();
}
public static void returnMapper(ObjectMapper mapper) {
pool.offer(mapper);
}
}
4.2 并行处理大JSON
对于超过1MB的大JSON,可以采用分块并行处理:
java复制public class ParallelJsonProcessor {
public static String processLargeJson(String json, int chunkSize) {
// 将JSON拆分为多个chunk
// 使用ForkJoinPool并行处理
// 合并处理结果
}
}
5. 实际应用案例
5.1 与Spring Boot集成
在Spring MVC中通过ResponseBodyAdvice实现自动压缩:
java复制@ControllerAdvice
public class JsonCompressAdvice implements ResponseBodyAdvice<Object> {
@Override
public boolean supports(MethodParameter returnType,
Class<? extends HttpMessageConverter<?>> converterType) {
return returnType.getMethodAnnotation(CompressResponse.class) != null;
}
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType,
MediaType selectedContentType,
Class<? extends HttpMessageConverter<?>> selectedConverterType,
ServerHttpRequest request, ServerHttpResponse response) {
// 压缩处理逻辑
}
}
5.2 缓存场景优化
Redis等缓存系统特别适合存储压缩后的JSON:
java复制public class CacheService {
private final RedisTemplate<String, String> redisTemplate;
public void setCompressed(String key, Object value) {
String json = JsonUtils.toJson(value);
String compressed = JsonCompressor.compress(json);
redisTemplate.opsForValue().set(key, compressed);
}
public <T> T getCompressed(String key, Class<T> type) {
String compressed = redisTemplate.opsForValue().get(key);
String json = JsonDecompressor.decompress(compressed);
return JsonUtils.fromJson(json, type);
}
}
6. 常见问题排查
6.1 压缩后数据损坏
症状:解压后的JSON结构与原始数据不一致
排查步骤:
- 检查键名映射字典是否完整
- 验证数值转换是否溢出
- 确认字符编码一致性(推荐强制使用UTF-8)
6.2 性能瓶颈
当处理超大JSON时可能出现OOM或响应缓慢:
- 增加JVM堆内存:-Xmx2g
- 采用流式处理代替DOM模型
- 对超过10MB的JSON考虑分片处理
6.3 移动端兼容性
某些老旧Android设备可能遇到的问题:
- 避免使用Java 8的Base64(改用Android SDK自带实现)
- 浮点数精度处理要兼容低版本系统
- 考虑提供压缩级别选项
我在实际项目中总结出一个经验:对于移动端API,建议保持压缩比在30%-50%之间。过度压缩虽然能减少传输量,但会增加客户端解压开销,反而可能降低整体性能。