package org.json; import static java.lang.String.format; import static java.util.Collections.emptyList; import java.io.UnsupportedEncodingException; import java.net.URLDecoder; import java.util.ArrayList; import java.util.List; /** * A JSON Pointer is a simple query language defined for JSON documents by * RFC 6901. */ public class JSONPointer { private List refTokens; /** * Pre-parses and initializes a new {@code JSONPointer} instance. If you want to * evaluate the same JSON Pointer on different JSON documents then it is recommended * to keep the {@code JSONPointer} instances due to performance considerations. * * @param pointer the JSON String or URI Fragment representation of the JSON pointer. * @throws IllegalArgumentException if {@code pointer} is not a valid JSON pointer */ public JSONPointer(String pointer) { if (pointer == null) { throw new NullPointerException("pointer cannot be null"); } if (pointer.isEmpty()) { refTokens = emptyList(); return; } if (pointer.startsWith("#/")) { pointer = pointer.substring(2); try { pointer = URLDecoder.decode(pointer, "utf-8"); } catch (UnsupportedEncodingException e) { throw new RuntimeException(e); } } else if (pointer.startsWith("/")) { pointer = pointer.substring(1); } else { throw new IllegalArgumentException("a JSON pointer should start with '/' or '#/'"); } refTokens = new ArrayList(); for (String token : pointer.split("/")) { refTokens.add(unescape(token)); } } private String unescape(String token) { return token.replace("~1", "/").replace("~0", "~") .replace("\\\"", "\"") .replace("\\\\", "\\"); } /** * Evaluates this JSON Pointer on the given {@code document}. The {@code document} * is usually a {@link JSONObject} or a {@link JSONArray} instance, but the empty * JSON Pointer ({@code ""}) can be evaluated on any JSON values and in such case the * returned value will be {@code document} itself. * * @param document the JSON document which should be the subject of querying. * @return the result of the evaluation * @throws JSONPointerException if an error occurs during evaluation */ public Object queryFrom(Object document) { if (refTokens.isEmpty()) { return document; } Object current = document; for (String token : refTokens) { if (current instanceof JSONObject) { current = ((JSONObject) current).opt(unescape(token)); } else if (current instanceof JSONArray) { current = readByIndexToken(current, token); } else { throw new JSONPointerException(format( "value [%s] is not an array or object therefore its key %s cannot be resolved", current, token)); } } return current; } private Object readByIndexToken(Object current, String indexToken) { try { int index = Integer.parseInt(indexToken); JSONArray currentArr = (JSONArray) current; if (index >= currentArr.length()) { throw new JSONPointerException(format("index %d is out of bounds - the array has %d elements", index, currentArr.length())); } return currentArr.get(index); } catch (NumberFormatException e) { throw new JSONPointerException(format("%s is not an array index", indexToken), e); } } }