Fixes #6558 - Allow configuring return type in JSON array parsing.
Introduced `arrayConverter` in both JSON and AsyncJSON.Factory.
Signed-off-by: Simone Bordet <simone.bordet@gmail.com>
(cherry picked from commit 342396c7ee
)
This commit is contained in:
parent
4e3e99c5c5
commit
266d8f0dca
|
@ -19,7 +19,9 @@ import java.util.Arrays;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
import org.eclipse.jetty.util.BufferUtil;
|
import org.eclipse.jetty.util.BufferUtil;
|
||||||
import org.eclipse.jetty.util.Index;
|
import org.eclipse.jetty.util.Index;
|
||||||
|
@ -62,6 +64,8 @@ import org.eclipse.jetty.util.ajax.JSON.Convertor;
|
||||||
* </pre>
|
* </pre>
|
||||||
* <p>Class {@code com.acme.Person} must either implement {@link Convertible},
|
* <p>Class {@code com.acme.Person} must either implement {@link Convertible},
|
||||||
* or be mapped with a {@link Convertor} via {@link Factory#putConvertor(String, Convertor)}.</p>
|
* or be mapped with a {@link Convertor} via {@link Factory#putConvertor(String, Convertor)}.</p>
|
||||||
|
* <p>JSON arrays are by default represented with a {@code List<Object>}, but the
|
||||||
|
* Java representation can be customized via {@link Factory#setArrayConverter(Function)}.</p>
|
||||||
*/
|
*/
|
||||||
public class AsyncJSON
|
public class AsyncJSON
|
||||||
{
|
{
|
||||||
|
@ -75,8 +79,31 @@ public class AsyncJSON
|
||||||
{
|
{
|
||||||
private Index.Mutable<CachedString> cache;
|
private Index.Mutable<CachedString> cache;
|
||||||
private Map<String, Convertor> convertors;
|
private Map<String, Convertor> convertors;
|
||||||
|
private Function<List<?>, Object> arrayConverter = list -> list;
|
||||||
private boolean detailedParseException;
|
private boolean detailedParseException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the function to customize the Java representation of JSON arrays
|
||||||
|
* @see #setArrayConverter(Function)
|
||||||
|
*/
|
||||||
|
public Function<List<?>, Object> getArrayConverter()
|
||||||
|
{
|
||||||
|
return arrayConverter;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Sets the function to convert JSON arrays from their default Java
|
||||||
|
* representation, a {@code List<Object>}, to another Java data structure
|
||||||
|
* such as an {@code Object[]}.</p>
|
||||||
|
*
|
||||||
|
* @param arrayConverter the function to customize the Java representation of JSON arrays
|
||||||
|
* @see #getArrayConverter()
|
||||||
|
*/
|
||||||
|
public void setArrayConverter(Function<List<?>, Object> arrayConverter)
|
||||||
|
{
|
||||||
|
this.arrayConverter = Objects.requireNonNull(arrayConverter);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return whether a parse failure should report the whole JSON string or just the last chunk
|
* @return whether a parse failure should report the whole JSON string or just the last chunk
|
||||||
*/
|
*/
|
||||||
|
@ -870,9 +897,10 @@ public class AsyncJSON
|
||||||
case ']':
|
case ']':
|
||||||
{
|
{
|
||||||
buffer.get();
|
buffer.get();
|
||||||
Object array = stack.peek().value;
|
@SuppressWarnings("unchecked")
|
||||||
|
List<Object> array = (List<Object>)stack.peek().value;
|
||||||
stack.pop();
|
stack.pop();
|
||||||
stack.peek().value(array);
|
stack.peek().value(convertArray(array));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
case ',':
|
case ',':
|
||||||
|
@ -1067,6 +1095,11 @@ public class AsyncJSON
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Object convertArray(List<?> array)
|
||||||
|
{
|
||||||
|
return factory.getArrayConverter().apply(array);
|
||||||
|
}
|
||||||
|
|
||||||
private Object convertObject(Map<String, Object> object)
|
private Object convertObject(Map<String, Object> object)
|
||||||
{
|
{
|
||||||
Object result = convertObject("x-class", object);
|
Object result = convertObject("x-class", object);
|
||||||
|
|
|
@ -19,11 +19,14 @@ import java.io.Reader;
|
||||||
import java.lang.reflect.Array;
|
import java.lang.reflect.Array;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
import org.eclipse.jetty.util.Loader;
|
import org.eclipse.jetty.util.Loader;
|
||||||
import org.eclipse.jetty.util.TypeUtil;
|
import org.eclipse.jetty.util.TypeUtil;
|
||||||
|
@ -81,6 +84,7 @@ public class JSON
|
||||||
|
|
||||||
private final Map<String, Convertor> _convertors = new ConcurrentHashMap<>();
|
private final Map<String, Convertor> _convertors = new ConcurrentHashMap<>();
|
||||||
private int _stringBufferSize = 1024;
|
private int _stringBufferSize = 1024;
|
||||||
|
private Function<List<?>, Object> _arrayConverter = List::toArray;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return the initial stringBuffer size to use when creating JSON strings
|
* @return the initial stringBuffer size to use when creating JSON strings
|
||||||
|
@ -461,7 +465,9 @@ public class JSON
|
||||||
*
|
*
|
||||||
* @param size the size of the array
|
* @param size the size of the array
|
||||||
* @return a new array representing the JSON array
|
* @return a new array representing the JSON array
|
||||||
|
* @deprecated use {@link #setArrayConverter(Function)} instead.
|
||||||
*/
|
*/
|
||||||
|
@Deprecated
|
||||||
protected Object[] newArray(int size)
|
protected Object[] newArray(int size)
|
||||||
{
|
{
|
||||||
return new Object[size];
|
return new Object[size];
|
||||||
|
@ -601,6 +607,28 @@ public class JSON
|
||||||
return _convertors.get(name);
|
return _convertors.get(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the function to customize the Java representation of JSON arrays
|
||||||
|
* @see #setArrayConverter(Function)
|
||||||
|
*/
|
||||||
|
public Function<List<?>, Object> getArrayConverter()
|
||||||
|
{
|
||||||
|
return _arrayConverter;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Sets the function to convert JSON arrays from their default Java
|
||||||
|
* representation, a {@code List<Object>}, to another Java data structure
|
||||||
|
* such as an {@code Object[]}.</p>
|
||||||
|
*
|
||||||
|
* @param arrayConverter the function to customize the Java representation of JSON arrays
|
||||||
|
* @see #getArrayConverter()
|
||||||
|
*/
|
||||||
|
public void setArrayConverter(Function<List<?>, Object> arrayConverter)
|
||||||
|
{
|
||||||
|
_arrayConverter = Objects.requireNonNull(arrayConverter);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <p>Parses the given JSON source into an object.</p>
|
* <p>Parses the given JSON source into an object.</p>
|
||||||
* <p>Although the JSON specification does not allow comments (of any kind)
|
* <p>Although the JSON specification does not allow comments (of any kind)
|
||||||
|
@ -928,14 +956,16 @@ public class JSON
|
||||||
switch (size)
|
switch (size)
|
||||||
{
|
{
|
||||||
case 0:
|
case 0:
|
||||||
return newArray(0);
|
list = Collections.emptyList();
|
||||||
|
break;
|
||||||
case 1:
|
case 1:
|
||||||
Object array = newArray(1);
|
list = Collections.singletonList(item);
|
||||||
Array.set(array, 0, item);
|
break;
|
||||||
return array;
|
|
||||||
default:
|
default:
|
||||||
return list.toArray(newArray(list.size()));
|
break;
|
||||||
}
|
}
|
||||||
|
return getArrayConverter().apply(list);
|
||||||
|
|
||||||
case ',':
|
case ',':
|
||||||
if (comma)
|
if (comma)
|
||||||
throw new IllegalStateException();
|
throw new IllegalStateException();
|
||||||
|
@ -970,6 +1000,7 @@ public class JSON
|
||||||
item = null;
|
item = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1199,7 +1230,7 @@ public class JSON
|
||||||
break doubleLoop;
|
break doubleLoop;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return Double.parseDouble(buffer.toString());
|
return Double.valueOf(buffer.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void seekTo(char seek, Source source)
|
protected void seekTo(char seek, Source source)
|
||||||
|
@ -1585,7 +1616,7 @@ public class JSON
|
||||||
*/
|
*/
|
||||||
public static class Literal implements Generator
|
public static class Literal implements Generator
|
||||||
{
|
{
|
||||||
private String _json;
|
private final String _json;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs a literal JSON instance.
|
* Constructs a literal JSON instance.
|
||||||
|
|
|
@ -19,15 +19,19 @@ import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
import org.hamcrest.Matchers;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.params.ParameterizedTest;
|
import org.junit.jupiter.params.ParameterizedTest;
|
||||||
import org.junit.jupiter.params.provider.MethodSource;
|
import org.junit.jupiter.params.provider.MethodSource;
|
||||||
import org.junit.jupiter.params.provider.ValueSource;
|
import org.junit.jupiter.params.provider.ValueSource;
|
||||||
|
|
||||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||||
|
import static org.hamcrest.MatcherAssert.assertThat;
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||||
import static org.junit.jupiter.api.Assertions.assertSame;
|
import static org.junit.jupiter.api.Assertions.assertSame;
|
||||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
@ -520,4 +524,47 @@ public class AsyncJSONTest
|
||||||
assertSame(foo, item);
|
assertSame(foo, item);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testArrayConverter()
|
||||||
|
{
|
||||||
|
// Test root arrays.
|
||||||
|
testArrayConverter("[1]", Function.identity());
|
||||||
|
|
||||||
|
// Test non-root arrays.
|
||||||
|
testArrayConverter("{\"array\": [1]}", object ->
|
||||||
|
{
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
Map<String, Object> map = (Map<String, Object>)object;
|
||||||
|
return map.get("array");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void testArrayConverter(String json, Function<Object, Object> extractor)
|
||||||
|
{
|
||||||
|
AsyncJSON.Factory factory = new AsyncJSON.Factory();
|
||||||
|
AsyncJSON async = factory.newAsyncJSON();
|
||||||
|
JSON sync = new JSON();
|
||||||
|
|
||||||
|
async.parse(UTF_8.encode(json));
|
||||||
|
Object result = extractor.apply(async.complete());
|
||||||
|
// AsyncJSON historically defaults to list.
|
||||||
|
assertThat(result, Matchers.instanceOf(List.class));
|
||||||
|
// JSON historically defaults to array.
|
||||||
|
result = extractor.apply(sync.parse(new JSON.StringSource(json)));
|
||||||
|
assertNotNull(result);
|
||||||
|
assertTrue(result.getClass().isArray(), json + " -> " + result);
|
||||||
|
|
||||||
|
// Configure AsyncJSON to return arrays.
|
||||||
|
factory.setArrayConverter(List::toArray);
|
||||||
|
async.parse(UTF_8.encode(json));
|
||||||
|
result = extractor.apply(async.complete());
|
||||||
|
assertNotNull(result);
|
||||||
|
assertTrue(result.getClass().isArray(), json + " -> " + result);
|
||||||
|
|
||||||
|
// Configure JSON to return lists.
|
||||||
|
sync.setArrayConverter(list -> list);
|
||||||
|
result = extractor.apply(sync.parse(new JSON.StringSource(json)));
|
||||||
|
assertThat(result, Matchers.instanceOf(List.class));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue