You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.22 09:21:24