java - 在 Java 中给定 JSON 路径/JSON 指针获取 JSON 文件的行号

标签 java json jsonpath jsonpointer

我正在寻找一种方法来解析特定节点的 JSON 文件并获取该节点在文件中的行号。我想使用 Jayway JSONPath 库来支持扩展 JSONPath 查询。

例如(来自 jsonpath.com),这里有一些 JSON:

{
  "firstName": "John",
  "lastName" : "doe",
  "age"      : 26,
  "address"  : {
    "streetAddress": "naist street",
    "city"         : "Nara",
    "postalCode"   : "630-0192"
  },
  "phoneNumbers": [
    {
      "type"  : "iPhone",
      "number": "0123-4567-8888"
    },
    {
      "type"  : "home",
      "number": "0123-4567-8910"
    }
  ]
}

这是一个 jsonPath: $.phoneNumbers.[?(@.type=='iPhone')]

我希望有一种方法可以表明该节点位于 json 文件中的第 11 行。我事先不知道 json 内容或 jsonPath 是什么。两者都是动态的。

到目前为止,我已经尝试将 json 解析为一棵树,并将其遍历到节点以获取解析器的当前位置,但解析器必须始终在 jsonPath 执行之前运行到文件末尾。还有其他想法吗?

最佳答案

我最终找到了一个涉及使用 Jackson 的 JsonFactory 和 JsonParser 的解决方案。至少可以说,这是一种拼凑,但它使用 JsonParser 对其解析器行号的了解来获取 JsonNode 的位置,并且工作得很好。

我将在此处粘贴代码,但代码也可以在 watchtower github 处获取。

调用类:

void findLineNumber() throws Exception{
    CustomParserFactory customParserFactory = new CustomParserFactory();
    ObjectMapper om = new ObjectMapper(customParserFactory);
    factory = new CustomJsonNodeFactory(om.getDeserializationConfig().getNodeFactory(),
            customParserFactory);
    om.setConfig(om.getDeserializationConfig().with(factory));
    config = Configuration.builder()
            .mappingProvider(new JacksonMappingProvider(om))
            .jsonProvider(new JacksonJsonNodeJsonProvider(om))
            .options(Option.ALWAYS_RETURN_LIST)
            .build();

    File filePath = ...;
    JsonPath jsonPath = ...;
    DocumentContext parsedDocument = JsonPath.parse(filePath, config);
    ArrayNode findings = parsedDocument.read(jsonPath);
    for (JsonNode finding : findings) {
        JsonLocation location = factory.getLocationForNode(finding);
        int lineNum = location.getLineNr();
        //Do something with lineNum
    }
}

CustomJsonNodeFactory.java

public class CustomJsonNodeFactory extends JsonNodeFactory {

    private static final long serialVersionUID = 8807395553661461181L;

    private final JsonNodeFactory delegate;
    private final CustomParserFactory parserFactory;

    /*
     * "Why isn't this a map?" you might be wondering. Well, when the nodes are created, they're all
     * empty and a node's hashCode is based on its children. So if you use a map and put the node
     * in, then the node's hashCode is based on no children, then when you lookup your node, it is
     * *with* children, so the hashcodes are different. Instead of all of this, you have to iterate
     * through a listing and find their matches once the objects have been populated, which is only
     * after the document has been completely parsed
     */
    private List<Entry<JsonNode, JsonLocation>> locationMapping;

    public CustomJsonNodeFactory(JsonNodeFactory nodeFactory,
        CustomParserFactory parserFactory) {
        delegate = nodeFactory;
        this.parserFactory = parserFactory;
        locationMapping = new ArrayList<>();
    }

    /**
     * Given a node, find its location, or null if it wasn't found
     * 
     * @param jsonNode the node to search for
     * @return the location of the node or null if not found
     */
    public JsonLocation getLocationForNode(JsonNode jsonNode) {
        return this.locationMapping.stream().filter(e -> e.getKey().equals(jsonNode))
                .map(e -> e.getValue()).findAny().orElse(null);
    }

    /**
     * Simple interceptor to mark the node in the lookup list and return it back
     * 
     * @param <T>  the type of the JsonNode
     * @param node the node itself
     * @return the node itself, having marked its location
     */
    private <T extends JsonNode> T markNode(T node) {
        JsonLocation loc = parserFactory.getParser().getCurrentLocation();
        locationMapping.add(new SimpleEntry<>(node, loc));
        return node;
    }

    @Override
    public BooleanNode booleanNode(boolean v) {
        return markNode(delegate.booleanNode(v));
    }

    @Override
    public NullNode nullNode() {
        return markNode(delegate.nullNode());
    }

    @Override
    public NumericNode numberNode(byte v) {
        return markNode(delegate.numberNode(v));
    }

    @Override
    public ValueNode numberNode(Byte value) {
        return markNode(delegate.numberNode(value));
    }

    @Override
    public NumericNode numberNode(short v) {
        return markNode(delegate.numberNode(v));
    }

    @Override
    public ValueNode numberNode(Short value) {
        return markNode(delegate.numberNode(value));
    }

    @Override
    public NumericNode numberNode(int v) {
        return markNode(delegate.numberNode(v));
    }

    @Override
    public ValueNode numberNode(Integer value) {
        return markNode(delegate.numberNode(value));
    }

    @Override
    public NumericNode numberNode(long v) {
        return markNode(delegate.numberNode(v));
    }

    @Override
    public ValueNode numberNode(Long value) {
        return markNode(delegate.numberNode(value));
    }

    @Override
    public ValueNode numberNode(BigInteger v) {
        return markNode(delegate.numberNode(v));
    }

    @Override
    public NumericNode numberNode(float v) {
        return markNode(delegate.numberNode(v));
    }

    @Override
    public ValueNode numberNode(Float value) {
        return markNode(delegate.numberNode(value));
    }

    @Override
    public NumericNode numberNode(double v) {
        return markNode(delegate.numberNode(v));
    }

    @Override
    public ValueNode numberNode(Double value) {
        return markNode(delegate.numberNode(value));
    }

    @Override
    public ValueNode numberNode(BigDecimal v) {
        return markNode(delegate.numberNode(v));
    }

    @Override
    public TextNode textNode(String text) {
        return markNode(delegate.textNode(text));
    }

    @Override
    public BinaryNode binaryNode(byte[] data) {
        return markNode(delegate.binaryNode(data));
    }

    @Override
    public BinaryNode binaryNode(byte[] data, int offset, int length) {
        return markNode(delegate.binaryNode(data, offset, length));
    }

    @Override
    public ValueNode pojoNode(Object pojo) {
        return markNode(delegate.pojoNode(pojo));
    }

    @Override
    public ValueNode rawValueNode(RawValue value) {
        return markNode(delegate.rawValueNode(value));
    }

    @Override
    public ArrayNode arrayNode() {
        return markNode(delegate.arrayNode());
    }

    @Override
    public ArrayNode arrayNode(int capacity) {
        return markNode(delegate.arrayNode(capacity));
    }

    @Override
    public ObjectNode objectNode() {
        return markNode(delegate.objectNode());
    }

}

CustomParserFactory.java(请注意,这会消除线程安全性,这可能是一件大事):

public class CustomParserFactory extends JsonFactory {

    private static final long serialVersionUID = -7523974986510864179L;
    private JsonParser parser;

    public JsonParser getParser() {
        return this.parser;
    }

    @Override
    public JsonParser createParser(Reader r) throws IOException, JsonParseException {
        parser = super.createParser(r);
        return parser;
    }

    @Override
    public JsonParser createParser(String content) throws IOException, JsonParseException {
        parser = super.createParser(content);
        return parser;
    }    
}

关于java - 在 Java 中给定 JSON 路径/JSON 指针获取 JSON 文件的行号,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/63585750/

相关文章:

java - Do/While 循环中的输出出现问题

java - 如何在GSON中使用@SerializedName备用字段来反序列化嵌套对象?

java - Jsonpath 显示具有动态名称的子项

java - 计数增量器错误

java - 解决吃 bean 游戏中上下移动时出现的问题

java - 读取组件 tynamo/PropertyEditBlocks :palette_set 的参数 'selected' 失败

javascript - jQuery .each() 仅打印外部 Json 中的最后一项

java - JSON 数组反序列化导致 Dalvik VM 崩溃

javascript - 添加值数组

c# - Json.Net - $.做?