我不确定您为什么会收到异常(没有任何勘误表吗?-我想知道为什么在使用相同的类加载器时不能将 A 类强制转换为 A 类)。另外请不要混合反序列化(映射到地图或普通对象)和JsonElement 子类。如果你这样做了,那么你必须使用正确的 types 和 tell 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 验证类型参数是否安全并匹配泛型类型签名。当然你可以使用其他的Type 或ParameterizedType 实现,但这似乎几乎是完美的(除了每个类型标记定义都会创建一个新类(那些...$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");
}