鉴于下面的 JSON,我目前有 Jackson 代码,它将把它解析成一个带有子对象数组的对象。 我可以轻松地将每个子对象序列化回字符串。
但是我发现很多极端情况,输出字符串与输入字符串不完全一样。 例如,输出字段顺序不一定与输入字段顺序匹配。 让我开始做这件事的极端情况是,我注意到如果我的子对象将“URL”字段声明为 URL,那么 ""
会转换为
null
,而不是 ""
,因此输出为 null
。所以我的问题是,有没有办法让 Jackson 将字符串分解为代表子对象的子字符串,而不是将其解析为实际的 Java 对象?
{
"foo": "bar",
"baz": [
{
"index": 1,
"url": ""
},
{
"url": "https://example.com",
"index": 2
}
]
}
换句话说,在上面的例子中,我想要的是一个有2个元素的List:
{
"index": 1,
"url": ""
}
和
{
"url": "https://example.com",
"index": 2
}
我疯了吗? 我需要编写一个自定义解串器吗? 我觉得杰克逊或多或少已经这样做了,我只是需要一种方法来实现它。
非常感谢。
编辑添加代码。
@JsonIgnoreProperties("foo")
@Builder @Value @Jacksonized
public static class Wrapper {
List<SubObject> baz;
}
@Builder @Value @Jacksonized
public static class SubObject {
Integer index;
URL url;
}
@Test
public void runTest() throws IOException {
ObjectMapper om = new ObjectMapper().registerModule(new VavrModule()); //for the Vavr List
Wrapper wrapper = om.readValue(new File("src/test/resources/fixtures/WrapperFile.json"), Wrapper.class);
List<SubObject> subObjects = wrapper.baz;
List<String> subObjectStrings = subObjects.map(so -> safeWriteValueAsString(om, so));
subObjectStrings.forEach(s -> System.out.println(s));
}
产生这个输出
{"index":1,"url":null}
{"index":2,"url":"https://example.com"}
请注意,这些都不是我想要的。
第二个子对象字符串的顺序错误。
第一个子对象的 URL 为 null 而不是“”。 虽然我可以通过将 URL 设为字符串而不是 URL 来处理 URL 问题,但这是一种可能的情况,而不是一般情况。
我想要的是类似于下面的内容(请注意,它无法编译。这是我正在寻找的东西,而不是我拥有的东西)。 我知道杰克逊必须能够读取字符串并决定子对象何时开始和结束。 应该可以获得子字符串和/或索引。 如果我有代表数组中每个对象的字符串,如果需要,我可以轻松使用 Jackson 将它们解析为实际的子对象。
// my theory of how it _could_ work
@Test
public void theoryTest() {
ObjectMapper om = new ObjectMapper().registerModule(new VavrModule()); //for the Vavr List
Map<String, String> propertyStrings = om.parseIntoPropertyStrings(f);
String bazString = propertyStrings.getOrElse("baz", "");
List<String> bazEntryStrings = om.parseIntoArray(bazString);
bazEntryStrings.forEach(s -> System.out.println(s);)
}
p.getCodec().readTree(p).toString();
。 这样你就拥有了原始字符串,这就是我想要的,
并且如果你愿意,你可以也“正常”反序列化它。 lombok 注释只是一个样板去除器,它不会改变我正在解决的问题。
代码
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import org.testng.annotations.Test;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
import io.vavr.collection.List;
import io.vavr.collection.Map;
import io.vavr.jackson.datatype.VavrModule;
import lombok.Builder;
import lombok.SneakyThrows;
import lombok.Value;
import lombok.extern.jackson.Jacksonized;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class WrapperSubObjectTest {
File f = new File("src/test/resources/fixtures/WrapperFile.json");
@Builder @Value @Jacksonized @JsonIgnoreProperties("foo")
public static class Wrapper {
List<SubObject> baz;
}
@Builder @Value @JsonDeserialize(using = SubObjectDeserializer.class)
public static class SubObject {
Integer index;
String url;
String originalString;
}
private static final ObjectMapper om = new ObjectMapper().registerModule(new VavrModule());
public static class SubObjectDeserializer extends StdDeserializer<SubObject> {
public SubObjectDeserializer() { this(SubObject.class); }
public SubObjectDeserializer(Class<?> vc) { super(vc); }
@Override
public SubObject deserialize(JsonParser p, DeserializationContext ctxt)
throws IOException, JsonProcessingException
{
String s = p.getCodec().readTree(p).toString();
TypeReference<Map<String, String>> mapTR = new TypeReference<Map<String, String>>() {};
Map<String, String> map = om.readValue(s, mapTR);
SubObject returnVal = new SubObject(Integer.parseInt(map.get("index").getOrElse("-1"))
, map.get("url").getOrElse("https://badvalue.com"), s);
return returnVal;
}
}
@Test
public void runTest() throws IOException {
log.info("contents of file \n{}\n\nParsing results", Files.readString(f.toPath()));
Wrapper wrapper = om.readValue(f, Wrapper.class);
List<SubObject> subObjects = wrapper.baz;
subObjects.forEach(so -> safeWriteValueAsString(om, so));
}
@SneakyThrows
private void safeWriteValueAsString(ObjectMapper om, SubObject so) {
log.info("\noriginalString = {}\nparsed = {}", so.originalString, om.writeValueAsString(so));
}
}
输出
contents of file
{
"foo": "bar",
"baz": [
{
"index": 1,
"url": ""
},
{
"url": "https://example.com",
"index": 2
}
]
}
Parsing results
originalString = {"index":1,"url":""}
parsed = {"index":1,"url":"","originalString":"{\"index\":1,\"url\":\"\"}"}
originalString = {"url":"https://example.com","index":2}
parsed = {"index":2,"url":"https://example.com","originalString":"{\"url\":\"https://example.com\",\"index\":2}"}