【问题标题】:Get the line number of a JSON file given a JSON Path/JSON Pointer in Java在 Java 中给定 JSON 路径/JSON 指针,获取 JSON 文件的行号
【发布时间】:2020-08-25 19:20:40
【问题描述】:

我正在寻找一种方法来解析特定节点的 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 执行之前运行到文件末尾。还有其他想法吗?

【问题讨论】:

  • 行号最终是无关紧要的,而且你也不能保证有多个行号。很可能所有的空格都被去掉了,而 JSON 是一行。
  • 虽然大体上如此,但这是静态分析引擎的一部分。 JSONPath 是规则,我想报告违反(发现)该规则的位置。 JSON 可能以某种方式被格式化,但不一定打印得很好。
  • 嘿@tophersmith116,你能找到解决办法吗?我完全有同样的要求。自过去 20 天以来,我一直在尝试解决这个问题,但还没有帮助。如果您能提供帮助,那就太好了。谢谢。
  • @AnkitOstwal 我已经发布了解决此问题的代码,其中包含线程安全警告。看看有没有帮助
  • 嘿@tophersmith116,感谢您的代码。让我检查一下,看看它是否对我的问题有帮助。我会及时通知你。

标签: java json jsonpath jsonpointer


【解决方案1】:

我最终找到了一个涉及使用 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;
    }    
}

【讨论】:

  • 嗨@tophersmith,这就像一个魅力。多亏了你,它解决了我三个多星期的问题。再次感谢。
  • 嗨@tophersmith,我在广泛的测试中发现,对于更简单的json,您的解决方案给出了正确的结果。但因为我对复杂 json 的解决方案失败了。
  • 嗨@tophersmith,如果你能对此提出一些见解。那会很好。我想弄清楚。但无法找到解决方案。帮助表示赞赏。谢谢。
  • 如果没有更多信息,我真的帮不了你。这适用于我的案例,但我不知道您所说的“复杂”案例是什么意思。我正在使用解析器中提供的信息...
【解决方案2】:

我想你可以使用 json 的漂亮打印版本,它应该返回你用换行符格式化的 json,然后从那里开始工作。

【讨论】:

    猜你喜欢
    • 2018-12-19
    • 2012-02-06
    • 1970-01-01
    • 2022-06-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多