package com.jayway.jsonpath.spi.json; import com.jayway.jsonpath.InvalidJsonException; import com.jayway.jsonpath.JsonPathException; import jakarta.json.JsonArray; import jakarta.json.JsonArrayBuilder; import jakarta.json.JsonBuilderFactory; import jakarta.json.JsonNumber; import jakarta.json.JsonObject; import jakarta.json.JsonObjectBuilder; import jakarta.json.JsonReader; import jakarta.json.JsonString; import jakarta.json.JsonStructure; import jakarta.json.JsonValue; import jakarta.json.spi.JsonProvider; import jakarta.json.stream.JsonParsingException; import java.io.ByteArrayInputStream; import java.io.InputStream; import java.io.InputStreamReader; import java.io.Reader; import java.io.StringReader; import java.io.UnsupportedEncodingException; import java.math.BigDecimal; import java.math.BigInteger; import java.nio.charset.StandardCharsets; import java.util.AbstractSet; import java.util.ArrayList; import java.util.Collection; import java.util.ConcurrentModificationException; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; import java.util.ListIterator; import java.util.Map; import java.util.Set; public class JakartaJsonProvider extends AbstractJsonProvider { private static final JsonProvider defaultJsonProvider = JsonProvider.provider(); private static final JsonBuilderFactory jsonBuilderFactory = defaultJsonProvider.createBuilderFactory(null); private final boolean mutableJson; /** * Constructs new instance of parsing and serialization adapter for Jakarta EE 9 * JSON-P default provider. JSON files, strings, and streams can be loaded, parsed, * and navigated with JsonPath expressions, and values retrieved - but no changes * to the loaded JSON document are permitted, and will yield exceptions. */ public JakartaJsonProvider() { this.mutableJson = false; } /** * Constructs new instance of parsing and serialization adapter for Jakarta EE 9 * JSON-P default provider, and optionally enables proxying of {@code JsonObject} * and {@code JsonArray} entities to implement mutable JSON structures. By default, * all structures and values produced and consumed by JSON-P are immutable. This * comes at an extra cost to perfomance and memory consumption, so enable only if * expected use cases include add/put/replace/delete operations on JSON document. * * @param mutableJson enable dynamic proxies for JSON structures */ public JakartaJsonProvider(boolean mutableJson) { this.mutableJson = mutableJson; } @Override public Object parse(String json) throws InvalidJsonException { return parse(new StringReader(json)); } @Override public Object parse(byte[] json) throws InvalidJsonException { return parse(new InputStreamReader(new ByteArrayInputStream(json), StandardCharsets.UTF_8)); } @Override public Object parse(InputStream jsonStream, String charset) throws InvalidJsonException { try { return parse(new InputStreamReader(jsonStream, charset)); } catch (UnsupportedEncodingException e) { throw new JsonPathException(e); } } private Object parse(Reader jsonInput) { try (JsonReader jsonReader = defaultJsonProvider.createReader(jsonInput)) { JsonStructure jsonStruct = jsonReader.read(); return mutableJson ? proxyAll(jsonStruct) : jsonStruct; } catch (JsonParsingException e) { throw new InvalidJsonException(e); } // not catching a JsonException as it never happens here } @Override public String toJson(Object obj) { if (obj instanceof JsonObjectBuilder) { obj = ((JsonObjectBuilder) obj).build(); } else if (obj instanceof JsonArrayBuilder) { obj = ((JsonArrayBuilder) obj).build(); } else if (obj instanceof List) { obj = jsonBuilderFactory.createArrayBuilder((Collection) obj).build(); } return obj.toString(); } @Override public Object createArray() { if (mutableJson) { return new JsonArrayProxy(jsonBuilderFactory.createArrayBuilder().build()); } else { return new LinkedList(); } } @Override public Object createMap() { if (mutableJson) { return new JsonObjectProxy(jsonBuilderFactory.createObjectBuilder().build()); } else { return jsonBuilderFactory.createObjectBuilder(); } } @Override public boolean isArray(Object obj) { return (obj instanceof JsonArray || obj instanceof JsonArrayBuilder || obj instanceof List); } @Override public Object getArrayIndex(Object obj, int idx) { if (obj instanceof JsonArrayBuilder) { obj = ((JsonArrayBuilder) obj).build(); } if (obj instanceof JsonArray) { return ((JsonArray) obj).get(idx); } else if (obj instanceof List) { return super.getArrayIndex(obj, idx); } else { throw new UnsupportedOperationException(); } } @Override public void setArrayIndex(Object array, int index, Object newValue) { if (array instanceof JsonArrayBuilder) { // next line is not optimal, but ArrayBuilder has no size() method if (index == ((JsonArrayBuilder) array).build().size()) { array = ((JsonArrayBuilder) array).add(wrap(newValue)); } else { array = ((JsonArrayBuilder) array).set(index, wrap(newValue)); } } else if (array instanceof JsonArray) { if (mutableJson && array instanceof JsonArrayProxy) { ((JsonArrayProxy) array).set(index, wrap(newValue)); } else { throw new UnsupportedOperationException("JsonArray is immutable in JSON-P"); } } else { super.setArrayIndex(array, index, wrap(newValue)); } } @Override public Object getMapValue(Object obj, String key) { if (obj instanceof JsonObjectBuilder) { obj = ((JsonObjectBuilder) obj).build(); } if (obj instanceof JsonObject) { JsonValue o = ((JsonObject) obj).get(key); if (o == null) { return UNDEFINED; } else { return unwrap(o); } } else { throw new UnsupportedOperationException(); } } @Override public void setProperty(Object obj, Object key, Object value) { if (obj instanceof JsonObjectBuilder) { ((JsonObjectBuilder) obj).add(key.toString(), wrap(value)); } else if (mutableJson && obj instanceof JsonObjectProxy) { ((JsonObjectProxy) obj).put(key.toString(), wrap(value)); } else if (obj instanceof JsonObject) { throw new UnsupportedOperationException("JsonObject is immutable in JSON-P"); } else if (obj instanceof JsonArrayBuilder) { if (key == null) { ((JsonArrayBuilder) obj).add(wrap(value)); } else { ((JsonArrayBuilder) obj).set(toArrayIndex(key), wrap(value)); } } else if (mutableJson && obj instanceof JsonArrayProxy) { if (key == null) { ((JsonArrayProxy) obj).add(wrap(value)); } else { ((JsonArrayProxy) obj).set(toArrayIndex(key), wrap(value)); } } else if (obj instanceof JsonArray) { throw new UnsupportedOperationException("JsonArray is immutable in JSON-P"); } else if (obj instanceof List) { @SuppressWarnings("unchecked") List array = (List) obj; if (key == null) { array.add(wrap(value)); } else { array.add(toArrayIndex(key), wrap(value)); } } else { throw new UnsupportedOperationException(); } } @SuppressWarnings("rawtypes") public void removeProperty(Object obj, Object key) { if (obj instanceof JsonObjectBuilder) { ((JsonObjectBuilder) obj).remove(key.toString()); } else if (obj instanceof JsonObject) { if (mutableJson && obj instanceof JsonObjectProxy) { ((JsonObjectProxy) obj).remove(key); } else { throw new UnsupportedOperationException("JsonObject is immutable in JSON-P"); } } else if (isArray(obj)) { int index = toArrayIndex(key).intValue(); if (obj instanceof JsonArrayBuilder) { ((JsonArrayBuilder) obj).remove(index); } else if (obj instanceof List) { // this also covers JsonArray as it implements List<> ((List) obj).remove(index); } } else { throw new UnsupportedOperationException(); } } @Override public boolean isMap(Object obj) { return (obj instanceof JsonObject || obj instanceof JsonObjectBuilder); } @Override public Collection getPropertyKeys(Object obj) { Set keys; if (obj instanceof JsonObjectBuilder) { keys = ((JsonObjectBuilder) obj).build().keySet(); } else if (obj instanceof JsonObject) { keys = ((JsonObject) obj).keySet(); } else { throw new UnsupportedOperationException("Json object is expected"); } return new ArrayList(keys); } @Override public int length(Object obj) { if (isArray(obj)) { if (obj instanceof JsonArrayBuilder) { return ((JsonArrayBuilder) obj).build().size(); } else { return ((List) obj).size(); } } else if (isMap(obj)) { if (obj instanceof JsonObjectBuilder) { obj = ((JsonObjectBuilder) obj).build(); } return ((JsonObject) obj).size(); } else { if (obj instanceof CharSequence) { return ((CharSequence) obj).length(); } } String className = obj != null ? obj.getClass().getName() : null; throw new JsonPathException("length operation can not applied to " + className); } @Override public Iterable toIterable(Object obj) { List values; if (isArray(obj)) { if (obj instanceof JsonArrayBuilder) { obj = ((JsonArrayBuilder) obj).build(); } values = new ArrayList(((List) obj).size()); for (Object val : ((List) obj)) { values.add(unwrap(val)); } } else if (isMap(obj)) { if (obj instanceof JsonObjectBuilder) { obj = ((JsonObjectBuilder) obj).build(); } values = new ArrayList(((JsonObject) obj).size()); for (JsonValue val : ((JsonObject) obj).values()) { values.add(unwrap(val)); } } else { throw new UnsupportedOperationException("an array or object instance is expected"); } return values; } @Override public Object unwrap(Object obj) { if (obj == null) { return null; } if (!(obj instanceof JsonValue)) { return obj; } switch (((JsonValue) obj).getValueType()) { case ARRAY: if (mutableJson && obj instanceof JsonArrayProxy) { return (JsonArray) obj; } else { return ((JsonArray) obj).getValuesAs((JsonValue v) -> unwrap(v)); } case STRING: return ((JsonString) obj).getString(); case NUMBER: if (((JsonNumber) obj).isIntegral()) { //return ((JsonNumber) obj).bigIntegerValueExact(); try { return ((JsonNumber) obj).intValueExact(); } catch (ArithmeticException e) { return ((JsonNumber) obj).longValueExact(); } } else { //return ((JsonNumber) obj).bigDecimalValue(); return ((JsonNumber) obj).doubleValue(); } case TRUE: return Boolean.TRUE; case FALSE: return Boolean.FALSE; case NULL: return null; default: return obj; } } private Integer toArrayIndex(Object index) { try { if (index instanceof Integer) { return (Integer) index; } else if (index instanceof Long) { return Integer.valueOf(((Long) index).intValue()); } else if (index != null) { return Integer.valueOf(index.toString()); } else { //return null; throw new IllegalArgumentException("Invalid array index"); } } catch (NumberFormatException e) { throw new JsonPathException(e); } } private JsonValue wrap(Object obj) { if (obj == null) { return JsonValue.NULL; } else if (obj instanceof JsonArray) { if (!mutableJson || obj instanceof JsonArrayProxy) { return (JsonArray) obj; } else { return proxyAll((JsonArray) obj); } } else if (obj instanceof JsonObject) { if (!mutableJson || obj instanceof JsonObjectProxy) { return (JsonObject) obj; } else { return proxyAll((JsonObject) obj); } } else if (obj instanceof JsonValue) { return (JsonValue) obj; } else if (Boolean.TRUE.equals(obj)) { return JsonValue.TRUE; } else if (Boolean.FALSE.equals(obj)) { return JsonValue.FALSE; } else if (obj instanceof CharSequence) { return defaultJsonProvider.createValue(obj.toString()); } else if (obj instanceof Number) { if (obj instanceof Integer) { int v = ((Number) obj).intValue(); return defaultJsonProvider.createValue(v); } else if (obj instanceof Long) { long v = ((Number) obj).longValue(); return defaultJsonProvider.createValue(v); } else if ((obj instanceof Float) || (obj instanceof Double)) { double v = ((Number) obj).doubleValue(); return defaultJsonProvider.createValue(v); } else if (obj instanceof BigInteger) { return defaultJsonProvider.createValue((BigInteger) obj); } else if (obj instanceof BigDecimal) { return defaultJsonProvider.createValue((BigDecimal) obj); } else { // default to BigDecimal conversion for other numeric types BigDecimal v = BigDecimal.valueOf(((Number) obj).doubleValue()); return defaultJsonProvider.createValue(v); } } else if (obj instanceof Collection) { JsonArray result = jsonBuilderFactory.createArrayBuilder((Collection) obj).build(); return mutableJson ? proxyAll(result) : result; } else if (obj instanceof Map) { @SuppressWarnings("unchecked") Map map = (Map) obj; JsonObject result = jsonBuilderFactory.createObjectBuilder(map).build(); return mutableJson ? proxyAll(result) : result; } else if (obj instanceof JsonArrayBuilder) { JsonArray result = ((JsonArrayBuilder) obj).build(); return mutableJson ? proxyAll(result) : result; } else if (obj instanceof JsonObjectBuilder) { JsonObject result = ((JsonObjectBuilder) obj).build(); return mutableJson ? proxyAll(result) : result; } else { String className = obj.getClass().getSimpleName(); throw new UnsupportedOperationException("Cannot create JSON element from " + className); } } private JsonStructure proxyAll(JsonStructure jsonStruct) { if (jsonStruct == null) { return null; } else if (jsonStruct instanceof JsonArrayProxy) { return (JsonArray) jsonStruct; } else if (jsonStruct instanceof JsonArray) { List array = new ArrayList<>(); for (JsonValue v : (JsonArray) jsonStruct) { if (v instanceof JsonStructure) { v = proxyAll((JsonStructure) v); } array.add(v); } return new JsonArrayProxy(jsonBuilderFactory.createArrayBuilder(array).build()); } else if (jsonStruct instanceof JsonObjectProxy) { return (JsonObject) jsonStruct; } else if (jsonStruct instanceof JsonObject) { Map map = new LinkedHashMap<>(); for (Map.Entry e : ((JsonObject) jsonStruct).entrySet()) { JsonValue v = e.getValue(); if (v instanceof JsonStructure) { v = proxyAll((JsonStructure) v); } map.put(e.getKey(), v); } return new JsonObjectProxy(jsonBuilderFactory.createObjectBuilder(map).build()); } else { throw new IllegalArgumentException(); } } private static class JsonArrayProxy implements JsonArray { private JsonArray arr; JsonArrayProxy(JsonArray arr) { this.arr = arr; } @Override public JsonObject getJsonObject(int index) { return arr.getJsonObject(index); } @Override public JsonArray getJsonArray(int index) { return arr.getJsonArray(index); } @Override public JsonNumber getJsonNumber(int index) { return arr.getJsonNumber(index); } @Override public JsonString getJsonString(int index) { return arr.getJsonString(index); } @Override public List getValuesAs(Class clazz) { return arr.getValuesAs(clazz); } @Override public String getString(int index) { return arr.getString(index); } @Override public String getString(int index, String defaultValue) { return arr.getString(index, defaultValue); } @Override public int getInt(int index) { return arr.getInt(index); } @Override public int getInt(int index, int defaultValue) { return arr.getInt(index, defaultValue); } @Override public boolean getBoolean(int index) { return arr.getBoolean(index); } @Override public boolean getBoolean(int index, boolean defaultValue) { return arr.getBoolean(index, defaultValue); } @Override public boolean isNull(int index) { return arr.isNull(index); } @Override public ValueType getValueType() { return arr.getValueType(); } @Override public int size() { return arr.size(); } @Override public boolean isEmpty() { return arr.isEmpty(); } @Override public boolean contains(Object o) { return arr.contains(o); } @Override public Iterator iterator() { return new Iterator() { final JsonArray refArr = arr; final Iterator it = arr.iterator(); @Override public boolean hasNext() { if (refArr == arr) { return it.hasNext(); } else { throw new ConcurrentModificationException(); } } @Override public JsonValue next() { if (refArr == arr) { return it.next(); } else { throw new ConcurrentModificationException(); } } }; } @Override public Object[] toArray() { return arr.toArray(); } @Override public T[] toArray(T[] a) { return arr.toArray(a); } @Override public boolean add(JsonValue e) { arr = jsonBuilderFactory.createArrayBuilder(arr).add(e).build(); return true; } @Override public boolean remove(Object o) { int i = arr.indexOf(o); if (i != -1) { arr = jsonBuilderFactory.createArrayBuilder(arr).remove(i).build(); return true; } else { return false; } } @Override public boolean containsAll(Collection c) { return arr.containsAll(c); } @Override public boolean addAll(Collection c) { if (!c.isEmpty()) { JsonArrayBuilder builder = jsonBuilderFactory.createArrayBuilder(arr); for (JsonValue v : c) { builder.add(v); } arr = builder.build(); return true; } else { return false; } } @Override public boolean addAll(int index, Collection c) { if (c.isEmpty()) { return false; } if (index < 0 || index >= arr.size()) { throw new IndexOutOfBoundsException(); } JsonArrayBuilder builder = jsonBuilderFactory.createArrayBuilder(arr); for (int i = 0; i < arr.size(); i++) { if (index == i) { for (JsonValue v : c) { builder.add(v); } } builder.add(arr.get(i)); } arr = builder.build(); return true; } @Override public boolean removeAll(Collection c) { if (c.isEmpty()) { return false; } JsonArrayBuilder builder = null; for (int i = 0, j = 0; i < arr.size(); i++, j++) { if (c.contains(arr.get(i))) { if (builder == null) { builder = jsonBuilderFactory.createArrayBuilder(arr); } builder.remove(j--); } } if (builder != null) { arr = builder.build(); return true; } else { return false; } } @Override public boolean retainAll(Collection c) { if (c.isEmpty()) { arr = jsonBuilderFactory.createArrayBuilder().build(); return true; } JsonArrayBuilder builder = null; for (int i = 0, j = 0; i < arr.size(); i++, j++) { if (!c.contains(arr.get(i))) { if (builder == null) { builder = jsonBuilderFactory.createArrayBuilder(arr); } builder.remove(j--); } } if (builder != null) { arr = builder.build(); return true; } else { return false; } } @Override public void clear() { arr = jsonBuilderFactory.createArrayBuilder().build(); } @Override public JsonValue get(int index) { return arr.get(index); } @Override public JsonValue set(int index, JsonValue element) { if (index == arr.size()) { arr = jsonBuilderFactory.createArrayBuilder(arr).add(index, element).build(); return null; } else { JsonValue oldValue = arr.get(index); arr = jsonBuilderFactory.createArrayBuilder(arr).set(index, element).build(); return oldValue; } } @Override public void add(int index, JsonValue element) { arr = jsonBuilderFactory.createArrayBuilder(arr).add(index, element).build(); } @Override public JsonValue remove(int index) { JsonValue oldValue = arr.get(index); arr = jsonBuilderFactory.createArrayBuilder(arr).remove(index).build(); return oldValue; } @Override public int indexOf(Object o) { return arr.indexOf(o); } @Override public int lastIndexOf(Object o) { return arr.lastIndexOf(o); } @Override public ListIterator listIterator() { return listIterator(0); } @Override public ListIterator listIterator(int index) { return new ListIterator() { final JsonArray refArr = arr; final ListIterator it = arr.listIterator(index); @Override public boolean hasNext() { if (refArr == arr) { return it.hasNext(); } else { throw new ConcurrentModificationException(); } } @Override public JsonValue next() { if (refArr == arr) { return it.next(); } else { throw new ConcurrentModificationException(); } } @Override public boolean hasPrevious() { if (refArr == arr) { return it.hasPrevious(); } else { throw new ConcurrentModificationException(); } } @Override public JsonValue previous() { if (refArr == arr) { return it.previous(); } else { throw new ConcurrentModificationException(); } } @Override public int nextIndex() { if (refArr == arr) { return it.nextIndex(); } else { throw new ConcurrentModificationException(); } } @Override public int previousIndex() { if (refArr == arr) { return it.previousIndex(); } else { throw new ConcurrentModificationException(); } } @Override public void remove() { it.remove(); // will throw exception } @Override public void set(JsonValue e) { it.set(e); // will throw exception } @Override public void add(JsonValue e) { it.add(e); // will throw exception } }; } @Override public List subList(int fromIndex, int toIndex) { return arr.subList(fromIndex, toIndex); } @Override public int hashCode() { return arr != null ? arr.hashCode() : 0; } @Override public boolean equals(Object obj) { if (obj == null) { return this.arr == null; } else if (obj instanceof JsonArrayProxy) { return this.arr.equals(((JsonArrayProxy) obj).arr); } return arr.equals(obj); } @Override public String toString() { return arr != null ? arr.toString() : null; } } private static class JsonObjectProxy implements JsonObject { private JsonObject obj; JsonObjectProxy(JsonObject obj) { this.obj = obj; } @Override public JsonArray getJsonArray(String name) { return obj.getJsonArray(name); } @Override public JsonObject getJsonObject(String name) { return obj.getJsonObject(name); } @Override public JsonNumber getJsonNumber(String name) { return obj.getJsonNumber(name); } @Override public JsonString getJsonString(String name) { return obj.getJsonString(name); } @Override public String getString(String name) { return obj.getString(name); } @Override public String getString(String name, String defaultValue) { return obj.getString(name, defaultValue); } @Override public int getInt(String name) { return obj.getInt(name); } @Override public int getInt(String name, int defaultValue) { return obj.getInt(name, defaultValue); } @Override public boolean getBoolean(String name) { return obj.getBoolean(name); } @Override public boolean getBoolean(String name, boolean defaultValue) { return obj.getBoolean(name, defaultValue); } @Override public boolean isNull(String name) { return obj.isNull(name); } @Override public ValueType getValueType() { return obj.getValueType(); } @Override public int size() { return obj.size(); } @Override public boolean isEmpty() { return obj.isEmpty(); } @Override public boolean containsKey(Object key) { return obj.containsKey(key); } @Override public boolean containsValue(Object value) { return obj.containsValue(value); } @Override public JsonValue get(Object key) { return obj.get(key); } @Override public JsonValue put(String key, JsonValue value) { JsonValue oldValue = obj.get(key); obj = jsonBuilderFactory.createObjectBuilder(obj).add(key, value).build(); return oldValue; } @Override public JsonValue remove(Object key) { JsonValue oldValue = obj.get(key); if (oldValue != null) { obj = jsonBuilderFactory.createObjectBuilder(obj).remove(key.toString()).build(); return oldValue; } else { return null; } } @Override public void putAll(Map m) { if (m.isEmpty()) { return; } JsonObjectBuilder builder = jsonBuilderFactory.createObjectBuilder(obj); for (Map.Entry e : m.entrySet()) { builder.add(e.getKey(), e.getValue()); } obj = builder.build(); } @Override public void clear() { obj = jsonBuilderFactory.createObjectBuilder().build(); } @Override public Set keySet() { return obj.keySet(); } @Override public Collection values() { return obj.values(); } @Override public Set> entrySet() { return new AbstractSet>() { final JsonObject refObj = obj; @Override public Iterator> iterator() { if (refObj != obj) { throw new ConcurrentModificationException(); } return new Iterator>() { final Iterator> it = obj.entrySet().iterator(); @Override public boolean hasNext() { if (refObj == obj) { return it.hasNext(); } else { throw new ConcurrentModificationException(); } } @Override public Map.Entry next() { if (refObj == obj) { return it.next(); } else { throw new ConcurrentModificationException(); } } }; } @Override public int size() { if (refObj == obj) { return obj.size(); } else { throw new ConcurrentModificationException(); } } }; } @Override public int hashCode() { return obj != null ? obj.hashCode() : 0; } @Override public boolean equals(Object obj) { if (obj == null) { return this.obj == null; } else if (obj instanceof JsonObjectProxy) { return this.obj.equals(((JsonObjectProxy) obj).obj); } return this.obj.equals(obj); } @Override public String toString() { return obj != null ? obj.toString() : null; } } }