【问题标题】:java.lang.ClassCastException: com.google.gson.internal.LinkedTreeMap cannot be cast to com.google.gson.internal.LinkedTreeMapjava.lang.ClassCastException:com.google.gson.internal.LinkedTreeMap 无法转换为 com.google.gson.internal.LinkedTreeMap
【发布时间】:2021-04-15 08:57:53
【问题描述】:

我正在尝试将 json 解析为 java 对象。这样做时,我收到异常“java.lang.ClassCastException:com.google.gson.internal.LinkedTreeMap 无法转换为 com.google.gson.internal.LinkedTreeMap”。不知何故,有时这段代码运行良好。这是代码。

import java.util.List;
import java.util.Map;

import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.internal.LinkedTreeMap;

public class Preference3 {
    
    public static void main(String[] args) {
        String jsonString1 = "{\"ca\":{\"industry\":[{\"path\":\"/abc/global/choice/in/financial\",\"type\":\"checked\"},{\"path\":\"/abc/global/choice/in/asset-management\",\"type\":\"checked\"}]},\"fr\":{\"country\":[{\"path\":\"/abc/global/choice/in/country/europe/italy\",\"type\":\"checked\"},{\"path\":\"/abc/global/choice/in/country/europe/switzerland\",\"type\":\"checked\"},{\"path\":\"/abc/global/choice/in/country/europe/sweden\",\"type\":\"checked\"}],\"services\":[{\"path\":\"/abc/global/choice/in/technology\",\"type\":\"checked\"},{\"path\":\"/abc/global/choice/in/technology/technology-media\",\"type\":\"checked\"},{\"path\":\"/abc/global/choice/in/technology/media\",\"type\":\"checked\"},{\"path\":\"/abc/global/choice/in/technology/telecommunications\",\"type\":\"checked\"}]}}";
        
        Map<String, LinkedTreeMap<String, List>> map = new Gson().fromJson(jsonString1, LinkedTreeMap.class);
        for (Map.Entry<String, LinkedTreeMap<String, List>> letterEntry : map.entrySet()) {
            String territory = letterEntry.getKey();
        
        for (Map.Entry<String, List> nameEntry : letterEntry.getValue().entrySet()) {
            String preferenceType = nameEntry.getKey();
            JsonArray preferenceElements = (JsonArray) new Gson().toJsonTree(nameEntry.getValue());
            for (JsonElement preferenceElement : preferenceElements) {
                JsonObject preferenceObj = preferenceElement.getAsJsonObject();
                String preferencePath = preferenceObj.get("path").getAsString();
                    System.out.println(preferencePath);
            }
            }
        }
    }}

还有其他更好的方法来解析这个 json。我浏览了一些博客,但没有找到任何其他解决方案,也无法直接将其适应任何 pojo。请帮忙

【问题讨论】:

  • 什么是属性cafr,它们是动态的还是将保持不变?
  • 动态属性
  • json 中是否有任何静态属性将保持不变或全部是动态的?

标签: java json gson


【解决方案1】:

我不确定您为什么会收到异常(没有任何勘误表吗?-我想知道为什么在使用相同的类加载器时不能将 A 类强制转换为 A 类)。另外请不要混合反序列化(映射到地图或普通对象)和JsonElement 子类。如果你这样做了,那么你必须使用正确的 typestell Gson 来使用它们。我还强烈建议不要坚持使用具体的类,特别是如果它们在内部包中——它们可能会在任何版本中改变,完全破坏你的代码。坚持接口。

还有其他更好的方法来解析这个 json。

是和不是。 Gson 更多的是 JSON 解析/写入和序列化/反序列化。如果问题与 Gson 中通常不是 JSON 查询库的问题接近,那么您所描述的内容。

我浏览了一些博客,但没有找到任何其他解决方案,也无法直接将其适应任何 pojo。

除非您知道所有键并确保可以在 DTO 中描述它们,否则您必须使用映射。

这里有一些更好的方法来查询您的 JSON 中的 path 字符串:

private static final Iterable<String> expected = ImmutableList.of(
        "/abc/global/choice/in/financial",
        "/abc/global/choice/in/asset-management",
        "/abc/global/choice/in/country/europe/italy",
        "/abc/global/choice/in/country/europe/switzerland",
        "/abc/global/choice/in/country/europe/sweden",
        "/abc/global/choice/in/technology",
        "/abc/global/choice/in/technology/technology-media",
        "/abc/global/choice/in/technology/media",
        "/abc/global/choice/in/technology/telecommunications"
);

您可以使用 JsonParser 类将整个 JSON 文档加载到内存中。 In 不使用反序列化和映射策略,因此它可能是使用 Gson JsonElement 及其子类表示 any JSON 元素的最快、最强大和最原生的方式。注意这里没有出现反序列化和Map

@Test
public void testWithJsonTree()
        throws IOException {
    try ( final JsonReader jsonReader = new JsonReader(new InputStreamReader(openJsonInputStream())) ) {
        final JsonElement jsonTree = JsonParser.parseReader(jsonReader);
        final Collection<String> actual = new ArrayList<>();
        for ( final Map.Entry<String, JsonElement> letterEntry : jsonTree.getAsJsonObject().entrySet() ) {
            for ( final Map.Entry<String, JsonElement> nameEntry : letterEntry.getValue().getAsJsonObject().entrySet() ) {
                for ( final JsonElement preferenceElement : nameEntry.getValue().getAsJsonArray() ) {
                    final String preferencePath = preferenceElement.getAsJsonObject().get("path").getAsString();
                    actual.add(preferencePath);
                }
            }
        }
        Assertions.assertIterableEquals(expected, actual);
    }
    try ( final JsonReader jsonReader = new JsonReader(new InputStreamReader(openJsonInputStream())) ) {
        final JsonElement jsonTree = JsonParser.parseReader(jsonReader);
        final Iterable<String> actual = jsonTree.getAsJsonObject()
                .entrySet()
                .stream()
                .flatMap(letterEntry -> letterEntry.getValue().getAsJsonObject().entrySet().stream()
                        .flatMap(nameEntry -> StreamSupport.stream(nameEntry.getValue().getAsJsonArray().spliterator(), false))
                        .map(preferenceElement -> preferenceElement.getAsJsonObject()
                                .get("path")
                                .getAsString()
                        )
                )
                .collect(ImmutableList.toImmutableList());
        Assertions.assertIterableEquals(expected, actual);
    }
}

如果你要反序列化它,你可以告诉 Gson concrete JSON 文档的类型。请注意,由于缺少正确的参数化,原始 Map 和 List 可能无法正常工作。编译时最简单和类型安全的方法是描述类型标记并使javac 验证类型参数是否安全并匹配泛型类型签名。当然你可以使用其他的TypeParameterizedType 实现,但这似乎几乎是完美的(除了每个类型标记定义都会创建一个新类(那些...$1.class 等等))。

private static final TypeToken<Map<String, Map<String, List<Map<String, String>>>>> elementMapTypeToken
        = new TypeToken<Map<String, Map<String, List<Map<String, String>>>>>() {};

private static final Gson gson = new GsonBuilder()
        .disableHtmlEscaping()
        .disableInnerClassSerialization()
        .create();

@Test
public void testWithDynamicMapping()
        throws IOException {
    try ( final JsonReader jsonReader = new JsonReader(new InputStreamReader(openJsonInputStream())) ) {
        final Map<String, Map<String, List<Map<String, String>>>> map = gson.fromJson(jsonReader, elementMapTypeToken.getType());
        final Iterable<String> actual = map.entrySet()
                .stream()
                .flatMap(letterEntry -> letterEntry.getValue().values().stream())
                .flatMap(Collection::stream)
                .map(preference -> preference.get("path"))
                .collect(ImmutableList.toImmutableList());
        Assertions.assertIterableEquals(expected, actual);
    }
}

如果将整个 JSON 文档加载到内存中可能会消耗内存并且耗费资源/时间,那么您可以使用流式传输并通过仅需要的标记嫁接来简单地读取 JSON 标记。当然,它可能看起来很难实现,但有时没有其他方法(顺便说一下,这种方法也可以处理无限流)。这是最快的方法,也可以与反序列化(Gson.fromJson(...) 系列方法)结合使用来选择更复杂的值,而不仅仅是简单的值。

@Test
public void testWithStreaming()
        throws IOException {
    try ( final JsonReader jsonReader = new JsonReader(new InputStreamReader(openJsonInputStream())) ) {
        jsonReader.beginObject();
        final Collection<String> actual = new ArrayList<>();
        while ( jsonReader.hasNext() ) {
            jsonReader.nextName();
            jsonReader.beginObject();
            while ( jsonReader.hasNext() ) {
                jsonReader.nextName();
                jsonReader.beginArray();
                while ( jsonReader.hasNext() ) {
                    jsonReader.beginObject();
                    while ( jsonReader.hasNext() ) {
                        final String name = jsonReader.nextName();
                        switch ( name ) {
                        case "path":
                            actual.add(jsonReader.nextString());
                            break;
                        default:
                            jsonReader.skipValue();
                            break;
                        }
                    }
                    jsonReader.endObject();
                }
                jsonReader.endArray();
            }
            jsonReader.endObject();
        }
        jsonReader.endObject();
        Assertions.assertIterableEquals(expected, actual);
    }
}

最后,您还可以使用查询库,比如 JsonPath,它可以转换描述 JSON 路径的简单 DSL 并自行遍历 JSON 文档。我不确定这种方法在内存和时间上的消耗情况,但我想它远不是最理想的,但由于 DSL,它非常简单。

private static final Configuration jsonPathConfiguration = Configuration.builder()
        .jsonProvider(new GsonJsonProvider(gson))
        .mappingProvider(new GsonMappingProvider(gson))
        .build();

@Test
public void testWithJsonPath()
        throws IOException {
    final ParseContext parseContext = JsonPath.using(jsonPathConfiguration);
    try ( final InputStream inputStream = openJsonInputStream() ) {
        final DocumentContext documentContext = parseContext.parse(inputStream);
        final Iterable<String> actual = StreamSupport.stream(documentContext.read("$.*.*.*.path", JsonArray.class).spliterator(), false)
                .map(JsonElement::getAsString)
                .collect(ImmutableList.toImmutableList());
        Assertions.assertIterableEquals(expected, actual);
    }
}

我还将您的 JSON 放入资源中,并使用自定义 openJsonInputStream() 读取它,以防止代码中出现沉重的 JSON。

private static InputStream openJsonInputStream() {
    return PreferenceTest.class.getResourceAsStream("preferences.json");
}

【讨论】:

    猜你喜欢
    • 2020-05-03
    • 2018-06-10
    • 2020-09-20
    • 1970-01-01
    • 1970-01-01
    • 2018-02-28
    • 1970-01-01
    • 2020-07-15
    • 2015-01-30
    相关资源
    最近更新 更多