获取JSON文件中与给定的JSON路径/JSON指针对应的行号,在Java中。

huangapple go评论102阅读模式
英文:

Get the line number of a JSON file given a JSON Path/JSON Pointer in Java

问题

我正在寻找一种解析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才会执行。还有其他想法吗?

英文:

I am looking for a way to parse a JSON file for a specific node and get that node's line number in the file. I would like to use the Jayway JSONPath library to support extended JSONPath queries.

For example (from jsonpath.com), here's some 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"
    }
  ]
}

and here's a jsonPath:
$.phoneNumbers.[?(@.type=='iPhone')]

I would like to have a way to say that this node is on line 11 in the json file. I don't know ahead of time what the json contents might be or the jsonPath. Both are dynamic.

So far, I've tried to parse the json into a tree and traverse it up to the node to get the parser's current location, but the parser must always run to the end of the file before the jsonPath executes. Any other ideas?

答案1

得分: 2

以下是翻译好的部分:

我最终找到了一个解决方案,涉及使用Jackson的JsonFactory和JsonParser。至少可以说这是一个权宜之计,但它利用了JsonParser对其解析器行号的了解,以获取JsonNode的位置,并且效果相当不错。

我将代码粘贴在这里,但该代码也可以在[watchtower github](https://github.com/tracelink/watchtower/tree/release-2.5/watchtower-module-json/src/main/java/com/tracelink/appsec/module/json/scanner)找到。

调用类:

    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();
            //对lineNum进行处理
        }
    }

CustomJsonNodeFactory.java:

    public class CustomJsonNodeFactory extends JsonNodeFactory {

	    private static final long serialVersionUID = 8807395553661461181L;
    
	    private final JsonNodeFactory delegate;
	    private final CustomParserFactory parserFactory;
    
	    /*
	     * "为什么这不是一个映射?" 你可能会想。好吧,在创建节点时,它们都是空的,而节点的哈希码基于其子节点。因此,如果使用映射并将节点放入其中,
	     * 那么节点的哈希码就是基于没有子节点的,然后当查找节点时,它带有子节点,因此哈希码是不同的。而不是所有这些,你必须在对象被填充后,
	     * 即在文档被完全解析后,通过列表进行迭代并找到它们的匹配项。
	     */
	    private List<Entry<JsonNode, JsonLocation>> locationMapping;
    
	    public CustomJsonNodeFactory(JsonNodeFactory nodeFactory,
			CustomParserFactory parserFactory) {
		    delegate = nodeFactory;
		    this.parserFactory = parserFactory;
		    locationMapping = new ArrayList<>();
	    }
    
	    /**
	     * 给定一个节点,找到它的位置,如果未找到则返回null
	     * 
	     * @param jsonNode 要查找的节点
	     * @return 节点的位置,如果未找到则返回null
	     */
	    public JsonLocation getLocationForNode(JsonNode jsonNode) {
	    	return this.locationMapping.stream().filter(e -> e.getKey().equals(jsonNode))
	    			.map(e -> e.getValue()).findAny().orElse(null);
	    }

    	/**
    	 * 用于标记查找列表中的节点并将其返回
    	 * 
    	 * @param <T>  JsonNode的类型
    	 * @param node 节点本身
    	 * @return 节点本身,标记了它的位置
	     */
	    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 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;
    	}    
    }

以上是您提供的内容的翻译部分。如有其他疑问,请随时提问。

英文:

I eventually found a solution that involves using Jackson's JsonFactory and JsonParser. It's kludge-y to say the least, but it uses the JsonParser's knowledge of its parser's line number to get the JsonNode's position and works pretty well.

I'll paste the code here, but the code is also available at watchtower github

Calling class:

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;
/*
* &quot;Why isn&#39;t this a map?&quot; you might be wondering. Well, when the nodes are created, they&#39;re all
* empty and a node&#39;s hashCode is based on its children. So if you use a map and put the node
* in, then the node&#39;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&lt;Entry&lt;JsonNode, JsonLocation&gt;&gt; locationMapping;
public CustomJsonNodeFactory(JsonNodeFactory nodeFactory,
CustomParserFactory parserFactory) {
delegate = nodeFactory;
this.parserFactory = parserFactory;
locationMapping = new ArrayList&lt;&gt;();
}
/**
* Given a node, find its location, or null if it wasn&#39;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 -&gt; e.getKey().equals(jsonNode))
.map(e -&gt; e.getValue()).findAny().orElse(null);
}
/**
* Simple interceptor to mark the node in the lookup list and return it back
* 
* @param &lt;T&gt;  the type of the JsonNode
* @param node the node itself
* @return the node itself, having marked its location
*/
private &lt;T extends JsonNode&gt; T markNode(T node) {
JsonLocation loc = parserFactory.getParser().getCurrentLocation();
locationMapping.add(new SimpleEntry&lt;&gt;(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 (Note that this removes thread-safety, which can be kind of a big deal):

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;
}    
}

答案2

得分: 0

我猜你可以使用漂亮打印版本的JSON,这应该会返回带有换行符的JSON格式,然后从那里开始处理。

英文:

I guess you could use the pretty print version of the json, which should return you the json formatted with the line breaks, and work from there.

huangapple
  • 本文由 发表于 2020年8月26日 03:20:40
  • 转载请务必保留本文链接:https://go.coder-hub.com/63585750.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定