使用Gson将json转换为Map.Entry对象

8

EASY VERSION

如果我让Gson将一些有效的json转换为MyMap,它可以轻松完成。

public class MyMap{
   Map<Long,String> content;
}


MyMap myMap = gson.fromJson(json, new TypeToken<MyMap>() {}.getType());

HARD VERSION:

我该如何让Gson实现以下功能?

public class MyDS{
    Map<Map.Entry<Long,String>,Map<Long,String>> content;
}

MyDS myDS = gson.fromJson(json, new TypeToken<MyDS>() {}.getType());

如果您确实需要,以下是JSON示例。

"content": {
      "[1, dog]": {
        "1": "max",
        "2": "pi",
        "3": "robot",
        "4": "catcher",
        "5": "reaper"
      },
      "[2, cat]": {
        "6": "black",
        "7": "white",
        "8": "meow",
        "9": "mice",
        "10": "rat"
      },
      "[3, rabbit]": {
        "16": "bunny",
        "17": "ears",
        "28": "burgerbun",
        "39": "alice",
        "50": "tweak"
      }
    }

更多笔记

为了保险起见,我尝试运行一个单元测试,只是试图用Gson读取json,然后我得到以下错误跟踪:

at sun.misc.Unsafe.allocateInstance(Native method)
java.lang.reflect.Method.invoke!(Native method)
com.google.gson.internal.UnsafeAllocator$1.newInstance(UnsafeAllocator.java:48)
com.google.gson.internal.ConstructorConstructor$14.construct(ConstructorConstructor.java:223)
com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.read(ReflectiveTypeAdapterFactory.java:207)
com.google.gson.internal.bind.TypeAdapterRuntimeTypeWrapper.read(TypeAdapterRuntimeTypeWrapper.java:40)
com.google.gson.internal.bind.MapTypeAdapterFactory$Adapter.read(MapTypeAdapterFactory.java:186)
com.google.gson.internal.bind.MapTypeAdapterFactory$Adapter.read(MapTypeAdapterFactory.java:145)
com.google.gson.Gson.fromJson(Gson.java:861)
com.google.gson.Gson.fromJson(Gson.java:826)
com.google.gson.Gson.fromJson(Gson.java:775)

无论键的形式为"[3, rabbit]"还是"{3, rabbit}",都不会影响其功能。


你能展示一下这个的JSON样例吗? - notionquest
引号只是因为我粘贴到文本编辑器中才这样。但它们没问题。就像我说的,它可以工作,直到我需要一个键入条目。 - Nouvel Travay
2个回答

4
假设您有一个有效的JSON内容,类型为:
{
   "content": {
      "[1, dog]": {
        "1": "max",
        "2": "pi",
        "3": "robot",
        "4": "catcher",
        "5": "reaper"
      },
      "[2, cat]": {
        "6": "black",
        "7": "white",
        "8": "meow",
        "9": "mice",
        "10": "rat"
      },
      "[3, rabbit]": {
        "16": "bunny",
        "17": "ears",
        "28": "burgerbun",
        "39": "alice",
        "50": "tweak"
      }
   }
}

为了实现你想要的功能,你可以简单地实现自己的Map.Entry Deserializer。因为它不是一个数组,所以不能直接反序列化,而{3,rabbit}也不是一个有效的JSON对象。
因此,你的Deserializer可以依赖于正则表达式来提取键和值,然后使用提取的值创建AbstractMap.SimpleEntry的实例,类似于以下代码:
public class MapEntryDeserializer implements JsonDeserializer<Map.Entry<Long, String>> {

    /**
     * Pattern corresponding to:
     * Starts with [
     * <a non empty sequence of digit characters>,
     * <a non empty sequence of any characters
     * Ends with ]
     */
    private static final Pattern PATTERN = Pattern.compile("^\\[(\\d+), ?(.+)\\]$");

    public Map.Entry<Long, String> deserialize(JsonElement json, Type typeOfT, 
        JsonDeserializationContext context) throws JsonParseException {
        // Extract the key/value pair from Strings of type [3, rabbit]
        String value = json.getAsString();
        Matcher matcher = PATTERN.matcher(value);
        if (!matcher.find()) {
            throw new JsonParseException(
                String.format("The map entry doesn't have the expected format: %s", value)
            );
        }
        return new AbstractMap.SimpleEntry<>(
            Long.valueOf(matcher.group(1)), matcher.group(2)
        );
    }
}

我可以使用以下方法反序列化我的 JSON 内容:

Type type = new TypeToken<MyDS>() {}.getType();
Gson gson = new GsonBuilder()
    .registerTypeAdapter(Map.Entry.class, new MapEntryDeserializer())
    .create();

MyDS myDS = gson.fromJson(json, type);

不错的解决方案,我不理解正则表达式的第二个组([^]]+),你能解释一下吗? - AxelH
2
@AxelH 这意味着任何字符的序列,除了] - Nicolas Filotto
我认为我们快要完成了。但是我一直收到以下错误提示: “地图条目格式不符合预期:1 = 狗”。 - Nouvel Travay
1
非常感谢。我使用value.split("[ ,=]")。+1,检查和悬赏。非常感谢。 - Nouvel Travay
@NouvelTravay 如果您喜欢,确实可以使用split,但是也要提取正则表达式以获得更好的性能。 - Nicolas Filotto
@NouvelTravay 请注意,使用 split 方法时不会验证格式,即使是 " 1[] dog," 这样的字符串也可以正常分割。 - Nicolas Filotto

1
根据Map.Entry的文档:
唯一获取映射条目的方法是从此集合视图的迭代器中获取。

https://docs.oracle.com/javase/8/docs/api/java/util/Map.Entry.html

这意味着在创建初始地图之前,您无法获取Map.Entry。为了实现您想要的功能,您需要将JSON解析为Map,然后迭代它以将其插入到您的MyDS对象中,保留HTML标签。
话虽如此,根据您的最终用途,在解析数据后重新组织/键入数据可能会有更好的方法。保留HTML标签。

这份文档提到了创建映射表条目的常规用法,但实际上您也可以创建一种Map.Entry的实例。甚至还有一些实用的实现方式,比如AbstractMap.SimpleEntry和AbstractMap.SimpleImmutableEntry。 - shmosel

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接