Browse Source
* Add support for JSON-P API (JSR 374) * Add support for JSON-B API (JSR 367) * Fix Jakarta EE 9 breaking unit tests * Remove Import-Package instr for JSON-P/JSON-B implementations * Fix whitespace in unit test classes * Proxy JSON-P objects and arrays to add mutability * Update project README for Jakarta JSON providers Co-authored-by: Leonid Malikov <leonid@percival.co.uk>pull/469/head
Leonid
3 years ago
committed by
GitHub
10 changed files with 2135 additions and 10 deletions
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,636 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2011 the original author or authors. |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
* you may not use this file except in compliance with the License. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
package com.jayway.jsonpath.spi.mapper; |
||||||
|
|
||||||
|
import java.lang.reflect.Constructor; |
||||||
|
import java.lang.reflect.GenericArrayType; |
||||||
|
import java.lang.reflect.Method; |
||||||
|
import java.lang.reflect.Modifier; |
||||||
|
import java.lang.reflect.ParameterizedType; |
||||||
|
import java.lang.reflect.Type; |
||||||
|
import java.math.BigDecimal; |
||||||
|
import java.math.BigInteger; |
||||||
|
import java.util.ArrayDeque; |
||||||
|
import java.util.Arrays; |
||||||
|
import java.util.Collection; |
||||||
|
import java.util.Deque; |
||||||
|
import java.util.Iterator; |
||||||
|
import java.util.List; |
||||||
|
import java.util.Map; |
||||||
|
import java.util.NoSuchElementException; |
||||||
|
import java.util.Queue; |
||||||
|
import java.util.Set; |
||||||
|
|
||||||
|
import com.jayway.jsonpath.Configuration; |
||||||
|
import com.jayway.jsonpath.TypeRef; |
||||||
|
|
||||||
|
import jakarta.json.JsonArray; |
||||||
|
import jakarta.json.JsonArrayBuilder; |
||||||
|
import jakarta.json.JsonException; |
||||||
|
import jakarta.json.JsonNumber; |
||||||
|
import jakarta.json.JsonObject; |
||||||
|
import jakarta.json.JsonObjectBuilder; |
||||||
|
import jakarta.json.JsonString; |
||||||
|
import jakarta.json.JsonStructure; |
||||||
|
import jakarta.json.JsonValue; |
||||||
|
import jakarta.json.bind.Jsonb; |
||||||
|
import jakarta.json.bind.JsonbBuilder; |
||||||
|
import jakarta.json.bind.JsonbConfig; |
||||||
|
import jakarta.json.bind.JsonbException; |
||||||
|
import jakarta.json.stream.JsonLocation; |
||||||
|
import jakarta.json.stream.JsonParser; |
||||||
|
|
||||||
|
public class JakartaMappingProvider implements MappingProvider { |
||||||
|
|
||||||
|
private final Jsonb jsonb; |
||||||
|
private Method jsonToClassMethod, jsonToTypeMethod; |
||||||
|
|
||||||
|
public JakartaMappingProvider() { |
||||||
|
this.jsonb = JsonbBuilder.create(); |
||||||
|
this.jsonToClassMethod = findMethod(jsonb.getClass(), "fromJson", JsonParser.class, Class.class); |
||||||
|
this.jsonToTypeMethod = findMethod(jsonb.getClass(), "fromJson", JsonParser.class, Type.class); |
||||||
|
} |
||||||
|
|
||||||
|
public JakartaMappingProvider(JsonbConfig jsonbConfiguration) { |
||||||
|
this.jsonb = JsonbBuilder.create(jsonbConfiguration); |
||||||
|
this.jsonToClassMethod = findMethod(jsonb.getClass(), "fromJson", JsonParser.class, Class.class); |
||||||
|
this.jsonToTypeMethod = findMethod(jsonb.getClass(), "fromJson", JsonParser.class, Type.class); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Maps supplied JSON source {@code Object} to a given target class or collection. |
||||||
|
* This implementation ignores the JsonPath's {@link Configuration} argument. |
||||||
|
*/ |
||||||
|
@Override |
||||||
|
public <T> T map(Object source, Class<T> targetType, Configuration configuration) { |
||||||
|
@SuppressWarnings("unchecked") |
||||||
|
T result = (T) mapImpl(source, targetType); |
||||||
|
return result; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Maps supplied JSON source {@code Object} to a given target type or collection. |
||||||
|
* This implementation ignores the JsonPath's {@link Configuration} argument. |
||||||
|
* <p> |
||||||
|
* Method <em>may</em> produce a {@code ClassCastException} on an attempt to cast |
||||||
|
* the result of JSON mapping operation to a requested target type, especially if |
||||||
|
* a parameterized generic type is used. |
||||||
|
*/ |
||||||
|
@Override |
||||||
|
public <T> T map(Object source, final TypeRef<T> targetType, Configuration configuration) { |
||||||
|
@SuppressWarnings("unchecked") |
||||||
|
T result = (T) mapImpl(source, targetType.getType()); |
||||||
|
return result; |
||||||
|
} |
||||||
|
|
||||||
|
private Object mapImpl(Object source, final Type targetType) { |
||||||
|
if (source == null || source == JsonValue.NULL) { |
||||||
|
return null; |
||||||
|
} |
||||||
|
if (source == JsonValue.TRUE) { |
||||||
|
if (Boolean.class.equals(targetType)) { |
||||||
|
return Boolean.TRUE; |
||||||
|
} else { |
||||||
|
String className = targetType.toString(); |
||||||
|
throw new MappingException("JSON boolean (true) cannot be mapped to " + className); |
||||||
|
} |
||||||
|
} |
||||||
|
if (source == JsonValue.FALSE) { |
||||||
|
if (Boolean.class.equals(targetType)) { |
||||||
|
return Boolean.FALSE; |
||||||
|
} else { |
||||||
|
String className = targetType.toString(); |
||||||
|
throw new MappingException("JSON boolean (false) cannot be mapped to " + className); |
||||||
|
} |
||||||
|
} else if (source instanceof JsonString) { |
||||||
|
if (String.class.equals(targetType)) { |
||||||
|
return ((JsonString) source).getChars(); |
||||||
|
} else { |
||||||
|
String className = targetType.toString(); |
||||||
|
throw new MappingException("JSON string cannot be mapped to " + className); |
||||||
|
} |
||||||
|
} else if (source instanceof JsonNumber) { |
||||||
|
JsonNumber jsonNumber = (JsonNumber) source; |
||||||
|
if (jsonNumber.isIntegral()) { |
||||||
|
return mapIntegralJsonNumber(jsonNumber, getRawClass(targetType)); |
||||||
|
} else { |
||||||
|
return mapDecimalJsonNumber(jsonNumber, getRawClass(targetType)); |
||||||
|
} |
||||||
|
} |
||||||
|
if (source instanceof JsonArrayBuilder) { |
||||||
|
source = ((JsonArrayBuilder) source).build(); |
||||||
|
} else if (source instanceof JsonObjectBuilder) { |
||||||
|
source = ((JsonObjectBuilder) source).build(); |
||||||
|
} |
||||||
|
if (source instanceof Collection) { |
||||||
|
// this covers both List<JsonValue> and JsonArray from JSON-P spec
|
||||||
|
Class<?> rawTargetType = getRawClass(targetType); |
||||||
|
Type targetTypeArg = getFirstTypeArgument(targetType); |
||||||
|
Collection<Object> result = newCollectionOfType(rawTargetType); |
||||||
|
for (Object srcValue : (Collection<?>) source) { |
||||||
|
if (srcValue instanceof JsonObject) { |
||||||
|
if (targetTypeArg != null) { |
||||||
|
result.add(mapImpl(srcValue, targetTypeArg)); |
||||||
|
} else { |
||||||
|
result.add(srcValue); |
||||||
|
} |
||||||
|
} else { |
||||||
|
result.add(unwrapJsonValue(srcValue)); |
||||||
|
} |
||||||
|
} |
||||||
|
return result; |
||||||
|
} else if (source instanceof JsonObject) { |
||||||
|
if (targetType instanceof Class) { |
||||||
|
if (jsonToClassMethod != null) { |
||||||
|
try { |
||||||
|
JsonParser jsonParser = new JsonStructureToParserAdapter((JsonStructure) source); |
||||||
|
return jsonToClassMethod.invoke(jsonb, jsonParser, (Class<?>) targetType); |
||||||
|
} catch (Exception e){ |
||||||
|
throw new MappingException(e); |
||||||
|
} |
||||||
|
} else { |
||||||
|
try { |
||||||
|
// Fallback databinding approach for JSON-B API implementations without
|
||||||
|
// explicit support for use of JsonParser in their public API. The approach
|
||||||
|
// is essentially first to serialize given value into JSON, and then bind
|
||||||
|
// it to data object of given class.
|
||||||
|
String json = source.toString(); |
||||||
|
return jsonb.fromJson(json, (Class<?>) targetType); |
||||||
|
} catch (JsonbException e){ |
||||||
|
throw new MappingException(e); |
||||||
|
} |
||||||
|
} |
||||||
|
} else if (targetType instanceof ParameterizedType) { |
||||||
|
if (jsonToTypeMethod != null) { |
||||||
|
try { |
||||||
|
JsonParser jsonParser = new JsonStructureToParserAdapter((JsonStructure) source); |
||||||
|
return jsonToTypeMethod.invoke(jsonb, jsonParser, (Type) targetType); |
||||||
|
} catch (Exception e){ |
||||||
|
throw new MappingException(e); |
||||||
|
} |
||||||
|
} else { |
||||||
|
try { |
||||||
|
// Fallback databinding approach for JSON-B API implementations without
|
||||||
|
// explicit support for use of JsonParser in their public API. The approach
|
||||||
|
// is essentially first to serialize given value into JSON, and then bind
|
||||||
|
// the JSON string to data object of given type.
|
||||||
|
String json = source.toString(); |
||||||
|
return jsonb.fromJson(json, (Type) targetType); |
||||||
|
} catch (JsonbException e){ |
||||||
|
throw new MappingException(e); |
||||||
|
} |
||||||
|
} |
||||||
|
} else { |
||||||
|
throw new MappingException("JSON object cannot be databind to " + targetType); |
||||||
|
} |
||||||
|
} else { |
||||||
|
return source; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@SuppressWarnings("unchecked") |
||||||
|
private <T> T mapIntegralJsonNumber(JsonNumber jsonNumber, Class<?> targetType) { |
||||||
|
if (targetType.isPrimitive()) { |
||||||
|
if (int.class.equals(targetType)) { |
||||||
|
return (T) Integer.valueOf(jsonNumber.intValueExact()); |
||||||
|
} else if (long.class.equals(targetType)) { |
||||||
|
return (T) Long.valueOf(jsonNumber.longValueExact()); |
||||||
|
} |
||||||
|
} else if (Integer.class.equals(targetType)) { |
||||||
|
return (T) Integer.valueOf(jsonNumber.intValueExact()); |
||||||
|
} else if (Long.class.equals(targetType)) { |
||||||
|
return (T) Long.valueOf(jsonNumber.longValueExact()); |
||||||
|
} else if (BigInteger.class.equals(targetType)) { |
||||||
|
return (T) jsonNumber.bigIntegerValueExact(); |
||||||
|
} else if (BigDecimal.class.equals(targetType)) { |
||||||
|
return (T) jsonNumber.bigDecimalValue(); |
||||||
|
} |
||||||
|
|
||||||
|
String className = targetType.getSimpleName(); |
||||||
|
throw new MappingException("JSON integral number cannot be mapped to " + className); |
||||||
|
} |
||||||
|
|
||||||
|
@SuppressWarnings("unchecked") |
||||||
|
private <T> T mapDecimalJsonNumber(JsonNumber jsonNumber, Class<?> targetType) { |
||||||
|
if (targetType.isPrimitive()) { |
||||||
|
if (float.class.equals(targetType)) { |
||||||
|
return (T) new Float(jsonNumber.doubleValue()); |
||||||
|
} else if (double.class.equals(targetType)) { |
||||||
|
return (T) Double.valueOf(jsonNumber.doubleValue()); |
||||||
|
} |
||||||
|
} else if (Float.class.equals(targetType)) { |
||||||
|
return (T) new Float(jsonNumber.doubleValue()); |
||||||
|
} else if (Double.class.equals(targetType)) { |
||||||
|
return (T) Double.valueOf(jsonNumber.doubleValue()); |
||||||
|
} else if (BigDecimal.class.equals(targetType)) { |
||||||
|
return (T) jsonNumber.bigDecimalValue(); |
||||||
|
} |
||||||
|
|
||||||
|
String className = targetType.getSimpleName(); |
||||||
|
throw new MappingException("JSON decimal number cannot be mapped to " + className); |
||||||
|
} |
||||||
|
|
||||||
|
private Object unwrapJsonValue(Object jsonValue) { |
||||||
|
if (jsonValue == null) { |
||||||
|
return null; |
||||||
|
} |
||||||
|
if (!(jsonValue instanceof JsonValue)) { |
||||||
|
return jsonValue; |
||||||
|
} |
||||||
|
switch (((JsonValue) jsonValue).getValueType()) { |
||||||
|
case ARRAY: |
||||||
|
// TODO do we unwrap JsonObjectArray proxies?
|
||||||
|
//return ((JsonArray) jsonValue).getValuesAs(JsonValue.class);
|
||||||
|
return ((JsonArray) jsonValue).getValuesAs((JsonValue v) -> unwrapJsonValue(v)); |
||||||
|
case OBJECT: |
||||||
|
throw new IllegalArgumentException("Use map() method to databind a JsonObject"); |
||||||
|
case STRING: |
||||||
|
return ((JsonString) jsonValue).getString(); |
||||||
|
case NUMBER: |
||||||
|
if (((JsonNumber) jsonValue).isIntegral()) { |
||||||
|
//return ((JsonNumber) jsonValue).bigIntegerValueExact();
|
||||||
|
try { |
||||||
|
return ((JsonNumber) jsonValue).intValueExact(); |
||||||
|
} catch (ArithmeticException e) { |
||||||
|
return ((JsonNumber) jsonValue).longValueExact(); |
||||||
|
} |
||||||
|
} else { |
||||||
|
//return ((JsonNumber) jsonValue).bigDecimalValue();
|
||||||
|
return ((JsonNumber) jsonValue).doubleValue(); |
||||||
|
} |
||||||
|
case TRUE: |
||||||
|
return Boolean.TRUE; |
||||||
|
case FALSE: |
||||||
|
return Boolean.FALSE; |
||||||
|
case NULL: |
||||||
|
return null; |
||||||
|
default: |
||||||
|
return jsonValue; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Creates new instance of {@code Collection} type specified by the |
||||||
|
* argument. If the argument refers to an interface, then a matching |
||||||
|
* Java standard implementation is returned; if it is a concrete class, |
||||||
|
* then method attempts to instantiate an object of that class given |
||||||
|
* there is a public no-arg constructor available. |
||||||
|
* |
||||||
|
* @param collectionType collection type; may be an interface or a class
|
||||||
|
* @return instance of collection type identified by the argument |
||||||
|
* @throws MappingException on a type that cannot be safely instantiated |
||||||
|
*/ |
||||||
|
private Collection<Object> newCollectionOfType(Class<?> collectionType) throws MappingException { |
||||||
|
if (Collection.class.isAssignableFrom(collectionType)) { |
||||||
|
if (!collectionType.isInterface()) { |
||||||
|
@SuppressWarnings("unchecked") |
||||||
|
Collection<Object> coll = (Collection<Object>) newNoArgInstance(collectionType); |
||||||
|
return coll; |
||||||
|
} else if (List.class.isAssignableFrom(collectionType)) { |
||||||
|
return new java.util.LinkedList<Object>(); |
||||||
|
} else if (Set.class.isAssignableFrom(collectionType)) { |
||||||
|
return new java.util.LinkedHashSet<Object>(); |
||||||
|
} else if (Queue.class.isAssignableFrom(collectionType)) { |
||||||
|
return new java.util.LinkedList<Object>(); |
||||||
|
} |
||||||
|
} |
||||||
|
String className = collectionType.getSimpleName(); |
||||||
|
throw new MappingException("JSON array cannot be mapped to " + className); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Lists all publicly accessible constructors for the {@code Class} |
||||||
|
* identified by the argument, including any constructors inherited |
||||||
|
* from superclasses, and uses a no-args constructor, if available, |
||||||
|
* to create a new instance of the class. If argument is interface, |
||||||
|
* this method returns {@code null}. |
||||||
|
* |
||||||
|
* @param targetType class type to create instance of |
||||||
|
* @return an instance of the class represented by the argument |
||||||
|
* @throws MappingException if no-arg public constructor is not there |
||||||
|
*/ |
||||||
|
private Object newNoArgInstance(Class<?> targetType) throws MappingException { |
||||||
|
if (targetType.isInterface()) { |
||||||
|
return null; |
||||||
|
} else { |
||||||
|
for (Constructor<?> ctr : targetType.getConstructors()) { |
||||||
|
if (ctr.getParameterCount() == 0) { |
||||||
|
try { |
||||||
|
return ctr.newInstance(); |
||||||
|
} catch (ReflectiveOperationException e) { |
||||||
|
throw new MappingException(e); |
||||||
|
} catch (IllegalArgumentException e) { |
||||||
|
// never happens
|
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
String className = targetType.getSimpleName(); |
||||||
|
throw new MappingException("Unable to find no-arg ctr for " + className); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Locates optional API method on the supplied JSON-B API implementation class with |
||||||
|
* the supplied name and parameter types. Searches the superclasses up to |
||||||
|
* {@code Object}, but ignores interfaces and default interface methods. Returns |
||||||
|
* {@code null} if no {@code Method} can be found. |
||||||
|
* |
||||||
|
* @param clazz the implementation class to reflect upon |
||||||
|
* @param name the name of the method |
||||||
|
* @param paramTypes the parameter types of the method |
||||||
|
* @return the {@code Method} reference, or {@code null} if none found |
||||||
|
*/ |
||||||
|
private Method findMethod(Class<?> clazz, String name, Class<?>... paramTypes) { |
||||||
|
while (clazz != null && !clazz.isInterface()) { |
||||||
|
for (Method method : clazz.getDeclaredMethods()) { |
||||||
|
final int mods = method.getModifiers(); |
||||||
|
if (Modifier.isPublic(mods) && !Modifier.isAbstract(mods) && |
||||||
|
name.equals(method.getName()) && |
||||||
|
Arrays.equals(paramTypes, method.getParameterTypes())) { |
||||||
|
return method; |
||||||
|
} |
||||||
|
} |
||||||
|
clazz = clazz.getSuperclass(); |
||||||
|
} |
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
private Class<?> getRawClass(Type targetType) { |
||||||
|
if (targetType instanceof Class) { |
||||||
|
return (Class<?>) targetType; |
||||||
|
} else if (targetType instanceof ParameterizedType) { |
||||||
|
return (Class<?>) ((ParameterizedType) targetType).getRawType(); |
||||||
|
} else if (targetType instanceof GenericArrayType) { |
||||||
|
String typeName = targetType.getTypeName(); |
||||||
|
throw new MappingException("Cannot map JSON element to " + typeName); |
||||||
|
} else { |
||||||
|
String typeName = targetType.getTypeName(); |
||||||
|
throw new IllegalArgumentException("TypeRef not supported: " + typeName); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private Type getFirstTypeArgument(Type targetType) { |
||||||
|
if (targetType instanceof ParameterizedType) { |
||||||
|
Type[] args = ((ParameterizedType) targetType).getActualTypeArguments(); |
||||||
|
if (args != null && args.length > 0) { |
||||||
|
if (args[0] instanceof Class) { |
||||||
|
return (Class<?>) args[0]; |
||||||
|
} else if (args[0] instanceof ParameterizedType) { |
||||||
|
return (ParameterizedType) args[0]; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Runtime adapter for {@link JsonParser} to pull the JSON objects and values from |
||||||
|
* a {@link JsonStructure} content tree instead of plain JSON string. |
||||||
|
* <p> |
||||||
|
* JSON-B API 1.0 final specification does not include any public methods to read JSON |
||||||
|
* content from pre-parsed {@link JsonStructure} tree, so this parser is used by the |
||||||
|
* Jakarta EE mapping provider above to feed in JSON content to JSON-B implementation. |
||||||
|
*/ |
||||||
|
private static class JsonStructureToParserAdapter implements JsonParser { |
||||||
|
|
||||||
|
private JsonStructureScope scope; |
||||||
|
private Event state; |
||||||
|
private final Deque<JsonStructureScope> ancestry = new ArrayDeque<>(); |
||||||
|
|
||||||
|
JsonStructureToParserAdapter(JsonStructure jsonStruct) { |
||||||
|
scope = createScope(jsonStruct); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public boolean hasNext() { |
||||||
|
return !((state == Event.END_ARRAY || state == Event.END_OBJECT) && ancestry.isEmpty()); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public Event next() { |
||||||
|
if (!hasNext()) { |
||||||
|
throw new NoSuchElementException(); |
||||||
|
} |
||||||
|
if (state == null) { |
||||||
|
state = scope instanceof JsonArrayScope ? Event.START_ARRAY : Event.START_OBJECT; |
||||||
|
} else { |
||||||
|
if (state == Event.END_ARRAY || state == Event.END_OBJECT) { |
||||||
|
scope = ancestry.pop(); |
||||||
|
} |
||||||
|
if (scope instanceof JsonArrayScope) { // array scope
|
||||||
|
if (scope.hasNext()) { |
||||||
|
scope.next(); |
||||||
|
state = getState(scope.getValue()); |
||||||
|
if (state == Event.START_ARRAY || state == Event.START_OBJECT) { |
||||||
|
ancestry.push(scope); |
||||||
|
scope = createScope(scope.getValue()); |
||||||
|
} |
||||||
|
} else { |
||||||
|
state = Event.END_ARRAY; |
||||||
|
} |
||||||
|
} else { // object scope
|
||||||
|
if (state == Event.KEY_NAME) { |
||||||
|
state = getState(scope.getValue()); |
||||||
|
if (state == Event.START_ARRAY || state == Event.START_OBJECT) { |
||||||
|
ancestry.push(scope); |
||||||
|
scope = createScope(scope.getValue()); |
||||||
|
} |
||||||
|
} else { |
||||||
|
if (scope.hasNext()) { |
||||||
|
scope.next(); |
||||||
|
state = Event.KEY_NAME; |
||||||
|
} else { |
||||||
|
state = Event.END_OBJECT; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
return state; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public String getString() { |
||||||
|
switch (state) { |
||||||
|
case KEY_NAME: |
||||||
|
return ((JsonObjectScope) scope).getKey(); |
||||||
|
case VALUE_STRING: |
||||||
|
return ((JsonString) scope.getValue()).getString(); |
||||||
|
case VALUE_NUMBER: |
||||||
|
return ((JsonNumber) scope.getValue()).toString(); |
||||||
|
default: |
||||||
|
throw new IllegalStateException("Parser is not in KEY_NAME, VALUE_STRING, or VALUE_NUMBER state"); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public boolean isIntegralNumber() { |
||||||
|
if (state == Event.VALUE_NUMBER) { |
||||||
|
return ((JsonNumber) scope.getValue()).isIntegral(); |
||||||
|
} |
||||||
|
throw new IllegalStateException("Target json value must a number, not " + state); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public int getInt() { |
||||||
|
if (state == Event.VALUE_NUMBER) { |
||||||
|
return ((JsonNumber) scope.getValue()).intValue(); |
||||||
|
} |
||||||
|
throw new IllegalStateException("Target json value must a number, not " + state); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public long getLong() { |
||||||
|
if (state == Event.VALUE_NUMBER) { |
||||||
|
return ((JsonNumber) scope.getValue()).longValue(); |
||||||
|
} |
||||||
|
throw new IllegalStateException("Target json value must a number, not " + state); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public BigDecimal getBigDecimal() { |
||||||
|
if (state == Event.VALUE_NUMBER) { |
||||||
|
return ((JsonNumber) scope.getValue()).bigDecimalValue(); |
||||||
|
} |
||||||
|
throw new IllegalStateException("Target json value must a number, not " + state); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public JsonLocation getLocation() { |
||||||
|
throw new UnsupportedOperationException("JSON-P adapter does not support getLocation()"); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void skipArray() { |
||||||
|
if (scope instanceof JsonArrayScope) { |
||||||
|
while (scope.hasNext()) { |
||||||
|
scope.next(); |
||||||
|
} |
||||||
|
state = Event.END_ARRAY; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void skipObject() { |
||||||
|
if (scope instanceof JsonObjectScope) { |
||||||
|
while (scope.hasNext()) { |
||||||
|
scope.next(); |
||||||
|
} |
||||||
|
state = Event.END_OBJECT; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void close() { |
||||||
|
// JSON objects are read-only
|
||||||
|
} |
||||||
|
|
||||||
|
private JsonStructureScope createScope(JsonValue value) { |
||||||
|
if (value instanceof JsonArray) { |
||||||
|
return new JsonArrayScope((JsonArray) value); |
||||||
|
} else if (value instanceof JsonObject) { |
||||||
|
return new JsonObjectScope((JsonObject) value); |
||||||
|
} |
||||||
|
throw new JsonException("Cannot create JSON iterator for " + value); |
||||||
|
} |
||||||
|
|
||||||
|
private Event getState(JsonValue value) { |
||||||
|
switch (value.getValueType()) { |
||||||
|
case ARRAY: |
||||||
|
return Event.START_ARRAY; |
||||||
|
case OBJECT: |
||||||
|
return Event.START_OBJECT; |
||||||
|
case STRING: |
||||||
|
return Event.VALUE_STRING; |
||||||
|
case NUMBER: |
||||||
|
return Event.VALUE_NUMBER; |
||||||
|
case TRUE: |
||||||
|
return Event.VALUE_TRUE; |
||||||
|
case FALSE: |
||||||
|
return Event.VALUE_FALSE; |
||||||
|
case NULL: |
||||||
|
return Event.VALUE_NULL; |
||||||
|
default: |
||||||
|
throw new JsonException("Unknown value type " + value.getValueType()); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private static abstract class JsonStructureScope implements Iterator<JsonValue> { |
||||||
|
/** |
||||||
|
* Returns current {@link JsonValue}, that the parser is pointing on. Before |
||||||
|
* the {@link #next()} method has been called, this returns {@code null}. |
||||||
|
* |
||||||
|
* @return JsonValue value object. |
||||||
|
*/ |
||||||
|
abstract JsonValue getValue(); |
||||||
|
} |
||||||
|
|
||||||
|
private static class JsonArrayScope extends JsonStructureScope { |
||||||
|
private final Iterator<JsonValue> it; |
||||||
|
private JsonValue value; |
||||||
|
|
||||||
|
JsonArrayScope(JsonArray array) { |
||||||
|
this.it = array.iterator(); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public boolean hasNext() { |
||||||
|
return it.hasNext(); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public JsonValue next() { |
||||||
|
value = it.next(); |
||||||
|
return value; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
JsonValue getValue() { |
||||||
|
return value; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private static class JsonObjectScope extends JsonStructureScope { |
||||||
|
private final Iterator<Map.Entry<String, JsonValue>> it; |
||||||
|
private JsonValue value; |
||||||
|
private String key; |
||||||
|
|
||||||
|
JsonObjectScope(JsonObject object) { |
||||||
|
this.it = object.entrySet().iterator(); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public boolean hasNext() { |
||||||
|
return it.hasNext(); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public JsonValue next() { |
||||||
|
Map.Entry<String, JsonValue> next = it.next(); |
||||||
|
this.key = next.getKey(); |
||||||
|
this.value = next.getValue(); |
||||||
|
return value; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
JsonValue getValue() { |
||||||
|
return value; |
||||||
|
} |
||||||
|
|
||||||
|
String getKey() { |
||||||
|
return key; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,381 @@ |
|||||||
|
package com.jayway.jsonpath; |
||||||
|
|
||||||
|
import org.junit.Test; |
||||||
|
|
||||||
|
import jakarta.json.JsonObject; |
||||||
|
import jakarta.json.JsonString; |
||||||
|
|
||||||
|
import java.io.InputStream; |
||||||
|
import java.util.HashMap; |
||||||
|
import java.util.List; |
||||||
|
import java.util.Map; |
||||||
|
|
||||||
|
import static com.jayway.jsonpath.JsonPath.parse; |
||||||
|
import static com.jayway.jsonpath.JsonPath.using; |
||||||
|
import static java.util.Collections.emptyMap; |
||||||
|
import static org.assertj.core.api.Assertions.assertThat; |
||||||
|
|
||||||
|
public class JakartaJsonProviderTest extends BaseTest { |
||||||
|
|
||||||
|
private static final Map<String, Object> EMPTY_MAP = emptyMap(); |
||||||
|
|
||||||
|
@Test |
||||||
|
public void an_object_can_be_read() { |
||||||
|
JsonObject book = using(JAKARTA_JSON_CONFIGURATION) |
||||||
|
.parse(JSON_DOCUMENT) |
||||||
|
.read("$.store.book[0]"); |
||||||
|
|
||||||
|
assertThat(((JsonString) book.get("author")).getChars()).isEqualTo("Nigel Rees"); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void a_property_can_be_read() { |
||||||
|
JsonString category = using(JAKARTA_JSON_CONFIGURATION) |
||||||
|
.parse(JSON_DOCUMENT) |
||||||
|
.read("$.store.book[0].category"); |
||||||
|
|
||||||
|
assertThat(category.getString()).isEqualTo("reference"); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void a_filter_can_be_applied() { |
||||||
|
List<Object> fictionBooks = using(JAKARTA_JSON_CONFIGURATION) |
||||||
|
.parse(JSON_DOCUMENT) |
||||||
|
.read("$.store.book[?(@.category == 'fiction')]"); |
||||||
|
|
||||||
|
assertThat(fictionBooks.size()).isEqualTo(3); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void result_can_be_mapped_to_object() { |
||||||
|
@SuppressWarnings("unchecked") |
||||||
|
List<Map<String, Object>> books = using(JAKARTA_JSON_CONFIGURATION) |
||||||
|
.parse(JSON_DOCUMENT) |
||||||
|
.read("$.store.book", List.class); |
||||||
|
|
||||||
|
assertThat(books.size()).isEqualTo(4); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void read_books_with_isbn() { |
||||||
|
List<Object> books = using(JAKARTA_JSON_CONFIGURATION).parse(JSON_DOCUMENT).read("$..book[?(@.isbn)]"); |
||||||
|
|
||||||
|
assertThat(books.size()).isEqualTo(2); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Functions take parameters, the length parameter for example takes an entire document which we anticipate |
||||||
|
* will compute to a document that is an array of elements which can determine its length. |
||||||
|
* |
||||||
|
* Since we translate this query from $..books.length() to length($..books) verify that this particular translation |
||||||
|
* works as anticipated. |
||||||
|
*/ |
||||||
|
@Test |
||||||
|
public void read_book_length_using_translated_query() { |
||||||
|
Integer result = using(JAKARTA_JSON_CONFIGURATION) |
||||||
|
.parse(JSON_BOOK_STORE_DOCUMENT) |
||||||
|
.read("$..book.length()"); |
||||||
|
assertThat(result).isEqualTo(4); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void read_book_length() { |
||||||
|
Object result = using(JAKARTA_JSON_CONFIGURATION) |
||||||
|
.parse(JSON_BOOK_STORE_DOCUMENT) |
||||||
|
.read("$.length($..book)"); |
||||||
|
assertThat(result).isEqualTo(4); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void issue_97() { |
||||||
|
String json = "{ \"books\": [ " + |
||||||
|
"{ \"category\": \"fiction\" }, " + |
||||||
|
"{ \"category\": \"reference\" }, " + |
||||||
|
"{ \"category\": \"fiction\" }, " + |
||||||
|
"{ \"category\": \"fiction\" }, " + |
||||||
|
"{ \"category\": \"reference\" }, " + |
||||||
|
"{ \"category\": \"fiction\" }, " + |
||||||
|
"{ \"category\": \"reference\" }, " + |
||||||
|
"{ \"category\": \"reference\" }, " + |
||||||
|
"{ \"category\": \"reference\" }, " + |
||||||
|
"{ \"category\": \"reference\" }, " + |
||||||
|
"{ \"category\": \"reference\" } ] }"; |
||||||
|
|
||||||
|
DocumentContext dc = using(JAKARTA_JSON_RW_CONFIGURATION) |
||||||
|
.parse(json) |
||||||
|
.delete("$.books[?(@.category == 'reference')]"); |
||||||
|
//System.out.println((Object) dc.read("$"));
|
||||||
|
@SuppressWarnings("unchecked") |
||||||
|
List<String> categories = dc.read("$..category", List.class); |
||||||
|
|
||||||
|
assertThat(categories).containsOnly("fiction"); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void test_delete_2() { |
||||||
|
DocumentContext dc = using(JAKARTA_JSON_RW_CONFIGURATION) |
||||||
|
.parse("[" + |
||||||
|
"{\"top\": {\"middle\": null}}," + |
||||||
|
"{\"top\": {\"middle\": {} }}," + |
||||||
|
"{\"top\": {\"middle\": {\"bottom\": 2} }}" + |
||||||
|
"]") |
||||||
|
.delete(JsonPath.compile("$[*].top.middle.bottom")); |
||||||
|
Object ans = dc.read("$"); |
||||||
|
//System.out.println(ans);
|
||||||
|
assert(ans.toString().equals("[{\"top\":{\"middle\":null}},{\"top\":{\"middle\":{}}},{\"top\":{\"middle\":{}}}]")); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void an_root_property_can_be_updated() { |
||||||
|
Object o = using(JAKARTA_JSON_RW_CONFIGURATION) |
||||||
|
.parse(JSON_DOCUMENT) |
||||||
|
.set("$.int-max-property", 1) |
||||||
|
.json(); |
||||||
|
|
||||||
|
Integer result = using(JAKARTA_JSON_RW_CONFIGURATION).parse(o) |
||||||
|
.read("$.int-max-property", Integer.class); |
||||||
|
|
||||||
|
assertThat(result).isEqualTo(1); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void an_deep_scan_can_update() { |
||||||
|
Object o = using(JAKARTA_JSON_RW_CONFIGURATION) |
||||||
|
.parse(JSON_DOCUMENT) |
||||||
|
.set("$..display-price", 1) |
||||||
|
.json(); |
||||||
|
|
||||||
|
List<Integer> result = using(JAKARTA_JSON_RW_CONFIGURATION).parse(o) |
||||||
|
.read("$..display-price", new TypeRef<List<Integer>>() {}); |
||||||
|
|
||||||
|
assertThat(result).containsExactly(1, 1, 1, 1, 1); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void an_filter_can_update() { |
||||||
|
final String updatePathFunction = "$.store.book[?(@.display-price)].display-price"; |
||||||
|
Object o = using(JAKARTA_JSON_RW_CONFIGURATION) |
||||||
|
.parse(JSON_DOCUMENT) |
||||||
|
.set(updatePathFunction, 1) |
||||||
|
.json(); |
||||||
|
|
||||||
|
List<Integer> result = using(JAKARTA_JSON_RW_CONFIGURATION).parse(o) |
||||||
|
.read(updatePathFunction, new TypeRef<List<Integer>>() {}); |
||||||
|
|
||||||
|
assertThat(result).containsExactly(1, 1, 1, 1); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void a_path_can_be_deleted() { |
||||||
|
final String deletePath = "$.store.book[*].display-price"; |
||||||
|
Object o = using(JAKARTA_JSON_RW_CONFIGURATION) |
||||||
|
.parse(JSON_DOCUMENT) |
||||||
|
.delete(deletePath) |
||||||
|
.json(); |
||||||
|
|
||||||
|
List<Integer> result = using(JAKARTA_JSON_RW_CONFIGURATION).parse(o) |
||||||
|
.read(deletePath, new TypeRef<List<Integer>>() {}); |
||||||
|
|
||||||
|
assertThat(result).isEmpty(); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void operations_can_chained() { |
||||||
|
Object o = using(JAKARTA_JSON_RW_CONFIGURATION) |
||||||
|
.parse(JSON_DOCUMENT) |
||||||
|
.delete("$.store.book[*].display-price") |
||||||
|
.set("$.store.book[*].category", "A") |
||||||
|
.json(); |
||||||
|
|
||||||
|
List<Integer> prices = using(JAKARTA_JSON_RW_CONFIGURATION).parse(o) |
||||||
|
.read("$.store.book[*].display-price", new TypeRef<List<Integer>>() {}); |
||||||
|
List<String> categories = using(JAKARTA_JSON_RW_CONFIGURATION).parse(o) |
||||||
|
.read("$.store.book[*].category", new TypeRef<List<String>>() {}); |
||||||
|
|
||||||
|
assertThat(prices).isEmpty(); |
||||||
|
assertThat(categories).containsExactly("A", "A", "A", "A"); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void an_array_index_can_be_updated() { |
||||||
|
String res = using(JAKARTA_JSON_RW_CONFIGURATION) |
||||||
|
.parse(JSON_DOCUMENT) |
||||||
|
.set("$.store.book[0]", "a") |
||||||
|
.read("$.store.book[0]", String.class); |
||||||
|
|
||||||
|
assertThat(res).isEqualTo("a"); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void an_array_slice_can_be_updated() { |
||||||
|
List<String> res = using(JAKARTA_JSON_RW_CONFIGURATION) |
||||||
|
.parse(JSON_DOCUMENT) |
||||||
|
.set("$.store.book[0:2]", "a") |
||||||
|
.read("$.store.book[0:2]", new TypeRef<List<String>>() {}); |
||||||
|
|
||||||
|
assertThat(res).containsExactly("a", "a"); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void an_array_criteria_can_be_updated() { |
||||||
|
List<String> res = using(JAKARTA_JSON_RW_CONFIGURATION) |
||||||
|
.parse(JSON_DOCUMENT) |
||||||
|
.set("$.store.book[?(@.category == 'fiction')]", "a") |
||||||
|
.read("$.store.book[?(@ == 'a')]", new TypeRef<List<String>>() {}); |
||||||
|
|
||||||
|
assertThat(res).containsExactly("a", "a", "a"); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void an_array_criteria_can_be_deleted() { |
||||||
|
List<String> res = using(JAKARTA_JSON_RW_CONFIGURATION) |
||||||
|
.parse(JSON_DOCUMENT) |
||||||
|
.delete("$.store.book[?(@.category == 'fiction')]") |
||||||
|
.read("$.store.book[*].category", new TypeRef<List<String>>() {}); |
||||||
|
|
||||||
|
assertThat(res).containsExactly("reference"); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void an_array_criteria_with_multiple_results_can_be_deleted(){ |
||||||
|
InputStream stream = this.getClass().getResourceAsStream("/json_array_multiple_delete.json"); |
||||||
|
String deletePath = "$._embedded.mandates[?(@.count=~/0/)]"; |
||||||
|
DocumentContext dc = using(JAKARTA_JSON_RW_CONFIGURATION).parse(stream).delete(deletePath); |
||||||
|
List<Object> result = dc.read(deletePath); |
||||||
|
assertThat(result.size()).isEqualTo(0); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void multi_prop_delete() { |
||||||
|
List<Map<String, Object>> res = using(JAKARTA_JSON_RW_CONFIGURATION) |
||||||
|
.parse(JSON_DOCUMENT) |
||||||
|
.delete("$.store.book[*]['author', 'category']") |
||||||
|
.read("$.store.book[*]['author', 'category']", new TypeRef<List<Map<String, Object>>>() {}); |
||||||
|
|
||||||
|
assertThat(res).containsExactly(EMPTY_MAP, EMPTY_MAP, EMPTY_MAP, EMPTY_MAP); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void multi_prop_update() { |
||||||
|
@SuppressWarnings("serial") |
||||||
|
Map<String, Object> expected = new HashMap<String, Object>(){{ |
||||||
|
put("author", "a"); |
||||||
|
put("category", "a"); |
||||||
|
}}; |
||||||
|
List<Map<String, Object>> res = using(JAKARTA_JSON_RW_CONFIGURATION) |
||||||
|
.parse(JSON_DOCUMENT) |
||||||
|
.set("$.store.book[*]['author', 'category']", "a") |
||||||
|
.read("$.store.book[*]['author', 'category']", new TypeRef<List<Map<String, Object>>>() {}); |
||||||
|
assertThat(res).containsExactly(expected, expected, expected, expected); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void add_to_array() { |
||||||
|
Object res = using(JAKARTA_JSON_RW_CONFIGURATION) |
||||||
|
.parse(JSON_DOCUMENT) |
||||||
|
.add("$.store.book", 1) |
||||||
|
.read("$.store.book[4]"); |
||||||
|
res = JAKARTA_JSON_RW_CONFIGURATION.jsonProvider().unwrap(res); |
||||||
|
assertThat(res).isEqualTo(1); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void add_to_object() { |
||||||
|
Object res = using(JAKARTA_JSON_RW_CONFIGURATION) |
||||||
|
.parse(JSON_DOCUMENT) |
||||||
|
.put("$.store.book[0]", "new-key", "new-value") |
||||||
|
.read("$.store.book[0].new-key"); |
||||||
|
res = JAKARTA_JSON_RW_CONFIGURATION.jsonProvider().unwrap(res); |
||||||
|
assertThat(res).isEqualTo("new-value"); |
||||||
|
} |
||||||
|
|
||||||
|
@Test(expected = InvalidModificationException.class) |
||||||
|
public void add_to_object_on_array() { |
||||||
|
using(JAKARTA_JSON_RW_CONFIGURATION).parse(JSON_DOCUMENT).put("$.store.book", "new-key", "new-value"); |
||||||
|
} |
||||||
|
|
||||||
|
@Test(expected = InvalidModificationException.class) |
||||||
|
public void add_to_array_on_object() { |
||||||
|
using(JAKARTA_JSON_RW_CONFIGURATION).parse(JSON_DOCUMENT).add("$.store.book[0]", "new-value"); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void a_path_can_be_renamed(){ |
||||||
|
Object o = using(JAKARTA_JSON_RW_CONFIGURATION) |
||||||
|
.parse(JSON_DOCUMENT) |
||||||
|
.renameKey("$.store", "book", "updated-book") |
||||||
|
.json(); |
||||||
|
List<Object> result = parse(o).read("$.store.updated-book"); |
||||||
|
|
||||||
|
assertThat(result).isNotEmpty(); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void map_array_items_can_be_renamed(){ |
||||||
|
Object o = using(JAKARTA_JSON_RW_CONFIGURATION) |
||||||
|
.parse(JSON_DOCUMENT) |
||||||
|
.renameKey("$.store.book[*]", "category", "renamed-category") |
||||||
|
.json(); |
||||||
|
List<Object> result = parse(o).read("$.store.book[*].renamed-category"); |
||||||
|
assertThat(result).isNotEmpty(); |
||||||
|
} |
||||||
|
|
||||||
|
@Test(expected = PathNotFoundException.class) |
||||||
|
public void non_existent_key_rename_not_allowed() { |
||||||
|
using(JAKARTA_JSON_RW_CONFIGURATION) |
||||||
|
.parse(JSON_DOCUMENT) |
||||||
|
.renameKey("$", "fake", "new-fake") |
||||||
|
.json(); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void single_match_value_can_be_mapped() { |
||||||
|
MapFunction mapFunction = new ToStringMapFunction(); |
||||||
|
String stringResult = using(JAKARTA_JSON_RW_CONFIGURATION) |
||||||
|
.parse(JSON_DOCUMENT) |
||||||
|
.map("$.string-property", mapFunction) |
||||||
|
.read("$.string-property", String.class); |
||||||
|
assertThat(stringResult.endsWith("converted")).isTrue(); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void object_can_be_mapped() { |
||||||
|
TypeRef<List<String>> typeRef = new TypeRef<List<String>>() {}; |
||||||
|
MapFunction mapFunction = new ToStringMapFunction(); |
||||||
|
DocumentContext dc = using(JAKARTA_JSON_RW_CONFIGURATION).parse(JSON_DOCUMENT); |
||||||
|
Object list = dc.read("$..book"); |
||||||
|
assertThat(list).isInstanceOf(List.class); |
||||||
|
Object res = dc.map("$..book", mapFunction).read("$..book", typeRef).get(0); |
||||||
|
assertThat(res).isInstanceOf(String.class); |
||||||
|
assertThat((String) res).endsWith("converted"); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void multi_match_path_can_be_mapped() { |
||||||
|
MapFunction mapFunction = new ToStringMapFunction(); |
||||||
|
List<Double> doubleResult = using(JAKARTA_JSON_RW_CONFIGURATION) |
||||||
|
.parse(JSON_DOCUMENT) |
||||||
|
.read("$..display-price", new TypeRef<List<Double>>() {}); |
||||||
|
for (Double dRes : doubleResult){ |
||||||
|
assertThat(dRes).isInstanceOf(Double.class); |
||||||
|
} |
||||||
|
List<String> stringResult = using(JAKARTA_JSON_RW_CONFIGURATION) |
||||||
|
.parse(JSON_DOCUMENT) |
||||||
|
.map("$..display-price", mapFunction) |
||||||
|
.read("$..display-price", new TypeRef<List<String>>() {}); |
||||||
|
for (String sRes : stringResult){ |
||||||
|
assertThat(sRes).isInstanceOf(String.class); |
||||||
|
assertThat(sRes.endsWith("converted")).isTrue(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Helper converter implementation for test cases.
|
||||||
|
private class ToStringMapFunction implements MapFunction { |
||||||
|
|
||||||
|
@Override |
||||||
|
public Object map(Object currentValue, Configuration configuration) { |
||||||
|
return currentValue.toString()+"converted"; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} |
Loading…
Reference in new issue