Java解析含重复键的PostgreSQL旧格式JSON的方法及疑问
处理Java中带有重复键的PostgreSQL旧版JSON解析问题
嘿,这个场景我太熟悉了——老版本PostgreSQL的JSON类型确实允许插入重复键,这和标准JSON规范里键必须唯一的要求冲突,所以常规的Java JSON库处理起来容易踩坑。下面我分几种常用的JSON库,给你讲讲怎么处理这种重复键的情况:
一、Jackson库的处理方式
Jackson是Java生态里最常用的JSON库之一,默认行为是用最后出现的键值对覆盖前面的,比如你例子里的"58",最后那个{"r":0,"g":255,"b":0}会被保留,前两个会被丢弃。
如果你想保留所有重复键的值,可以自定义一个Map的反序列化器,把重复键对应的值自动转成List:
import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.JsonDeserializer; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.module.SimpleModule; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; public class DuplicateKeyJsonDeserializer extends JsonDeserializer<Map<String, Object>> { @Override public Map<String, Object> deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException { Map<String, Object> map = new HashMap<>(); while (p.nextToken() != null) { if (p.getCurrentToken().isFieldName()) { String key = p.getCurrentName(); p.nextToken(); Object value = ctxt.readValue(p, Object.class); // 处理重复键:如果已存在,转成List存储 if (map.containsKey(key)) { Object existingValue = map.get(key); if (existingValue instanceof List) { ((List) existingValue).add(value); } else { List<Object> list = new ArrayList<>(); list.add(existingValue); list.add(value); map.put(key, list); } } else { map.put(key, value); } } } return map; } public static void main(String[] args) throws IOException { String json = "{ \"61\":{\"value\":5,\"to_value\":5}, \"58\":{\"r\":0,\"g\":0,\"b\":255}, \"58\":{\"r\":165,\"g\":42,\"b\":42}, \"58\":{\"r\":0,\"g\":255,\"b\":0}, \"63\":{\"r\":0,\"g\":0,\"b\":0}, \"57\":{\"r\":0,\"g\":0,\"b\":255},\"57\":{\"r\":0,\"g\":255,\"b\":0}}"; ObjectMapper mapper = new ObjectMapper(); SimpleModule module = new SimpleModule(); module.addDeserializer(Map.class, new DuplicateKeyJsonDeserializer()); mapper.registerModule(module); Map<String, Object> result = mapper.readValue(json, Map.class); // 输出测试:"58"会返回包含三个颜色对象的List System.out.println(result.get("58")); } }
如果你的需求是遇到重复键就抛出异常(避免数据丢失),可以开启Jackson的严格模式:
ObjectMapper mapper = new ObjectMapper(); mapper.enable(DeserializationFeature.FAIL_ON_READING_DUP_TREE_KEY); // 此时解析带重复键的JSON会抛出JsonParseException
二、Gson库的处理方式
Gson的默认行为和Jackson一样,也是用最后出现的键值对覆盖前面的。要保留所有重复键,需要自定义TypeAdapterFactory:
import com.google.gson.Gson; import com.google.gson.TypeAdapter; import com.google.gson.TypeAdapterFactory; import com.google.gson.reflect.TypeToken; import com.google.gson.stream.JsonReader; import com.google.gson.stream.JsonToken; import com.google.gson.stream.JsonWriter; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; public class DuplicateKeyMapAdapterFactory implements TypeAdapterFactory { @SuppressWarnings("unchecked") @Override public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) { if (!Map.class.isAssignableFrom(type.getRawType())) { return null; } TypeAdapter<T> delegate = gson.getDelegateAdapter(this, type); return (TypeAdapter<T>) new TypeAdapter<Map<String, Object>>() { @Override public void write(JsonWriter out, Map<String, Object> value) throws IOException { delegate.write(out, (T) value); } @Override public Map<String, Object> read(JsonReader in) throws IOException { Map<String, Object> map = new HashMap<>(); in.beginObject(); while (in.hasNext()) { String key = in.nextName(); Object value = gson.fromJson(in, Object.class); // 处理重复键逻辑 if (map.containsKey(key)) { Object existing = map.get(key); if (existing instanceof List) { ((List<Object>) existing).add(value); } else { List<Object> list = new ArrayList<>(); list.add(existing); list.add(value); map.put(key, list); } } else { map.put(key, value); } } in.endObject(); return map; } }; } public static void main(String[] args) { String json = "{ \"61\":{\"value\":5,\"to_value\":5}, \"58\":{\"r\":0,\"g\":0,\"b\":255}, \"58\":{\"r\":165,\"g\":42,\"b\":42}, \"58\":{\"r\":0,\"g\":255,\"b\":0}, \"63\":{\"r\":0,\"g\":0,\"b\":0}, \"57\":{\"r\":0,\"g\":0,\"b\":255},\"57\":{\"r\":0,\"g\":255,\"b\":0}}"; Gson gson = new Gson().newBuilder() .registerTypeAdapterFactory(new DuplicateKeyMapAdapterFactory()) .create(); Map<String, Object> result = gson.fromJson(json, Map.class); System.out.println(result.get("58")); } }
三、从源头解决:在PostgreSQL端处理重复键
其实最稳妥的方式是在导出JSON之前,先在PostgreSQL里把重复键合并成数组,这样导出的就是标准JSON,Java解析起来完全不用特殊处理。比如用json_agg函数:
-- 假设你的数据来自某个表,比如json_data表,有key和value两个字段 SELECT json_object_agg(key, value_list) AS standard_json FROM ( SELECT key, json_agg(value) AS value_list -- 把同一个key的所有value合并成数组 FROM json_data GROUP BY key ) AS aggregated_data;
这样导出的JSON里,每个key对应的值都是数组,比如"58": [{"r":0,"g":0,"b":255}, {"r":165,"g":42,"b":42}, {"r":0,"g":255,"b":0}],直接用Jackson/Gson解析成Map<String, List<YourObject>>就行,完全符合标准JSON规范。
内容的提问来源于stack exchange,提问作者user2903536
相关产品推荐
相关产品推荐

