Skip to content

Commit

Permalink
More lenient JSON handling
Browse files Browse the repository at this point in the history
* Accept JSON arrays as List/array
* Accept JSON map as Map
* Handle boxing when cehcking types
* Adjust exception handling
  • Loading branch information
jodastephen committed Dec 24, 2024
1 parent 7453f8b commit 6a052ed
Show file tree
Hide file tree
Showing 4 changed files with 67 additions and 57 deletions.
67 changes: 45 additions & 22 deletions src/main/java/org/joda/beans/ser/json/AbstractJsonReader.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,20 @@
import static org.joda.beans.ser.json.JodaBeanJsonWriter.TYPE;
import static org.joda.beans.ser.json.JodaBeanJsonWriter.VALUE;

import java.io.IOException;
import java.io.UncheckedIOException;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

import org.joda.beans.Bean;
import org.joda.beans.JodaBeanUtils;
import org.joda.beans.MetaProperty;
import org.joda.beans.ResolvedType;
import org.joda.beans.ser.JodaBeanSer;
import org.joda.beans.ser.SerCategory;
import org.joda.beans.ser.SerIterable;
import org.joda.beans.ser.SerIteratorFactory;
import org.joda.beans.ser.SerOptional;
import org.joda.beans.ser.SerTypeMapper;

Expand Down Expand Up @@ -76,16 +81,24 @@ abstract class AbstractJsonReader {
* @param input the JSON input
* @param declaredType the declared type, not null
* @return the bean, not null
* @throws Exception if an error occurs
* @throws UncheckedIOException if unable to read the stream
* @throws IllegalArgumentException if unable to parse the JSON
* @throws ClassCastException if the root type does not match the type in the JSON
*/
<T> T parseRoot(JsonInput input, Class<T> declaredType) throws Exception {
this.input = input;
var parsed = parseObject(input.acceptEvent(JsonEvent.OBJECT), declaredType, null, null, null, true);
return declaredType.cast(parsed);
<T> T parseRoot(JsonInput input, Class<T> declaredType) {
try {
this.input = input;
var parsed = parseObject(input.acceptEvent(JsonEvent.OBJECT), declaredType, null, null, null, true);
return declaredType.cast(parsed);
} catch (ClassNotFoundException ex) {
throw new IllegalArgumentException(ex);
} catch (IOException ex) {
throw new UncheckedIOException(ex);
}
}

// parse a bean, event after object start passed in
private Object parseBean(JsonEvent event, Class<?> beanType) {
private Object parseBean(JsonEvent event, Class<?> beanType) throws IOException {
var propName = "";
try {
var deser = settings.getDeserializers().findDeserializer(beanType);
Expand All @@ -107,9 +120,11 @@ private Object parseBean(JsonEvent event, Class<?> beanType) {
event = input.acceptObjectSeparator();
}
return deser.build(beanType, builder);
} catch (IOException ex) {
throw ex;
} catch (Exception ex) {
throw new IllegalArgumentException(
"Error parsing bean: " + beanType.getName() + "::" + propName + ", " + ex.getMessage(), ex);
"Error parsing bean: " + beanType.getName() + "::" + propName + ": " + ex.getMessage(), ex);
}
}

Expand All @@ -120,7 +135,7 @@ private Object parseObject(
MetaProperty<?> metaProp,
Class<?> beanType,
SerIterable parentIterable,
boolean rootType) throws Exception {
boolean rootType) throws IOException, ClassNotFoundException {

// avoid nulls
var declaredType = (inputDeclaredType == null ? Object.class : inputDeclaredType);
Expand Down Expand Up @@ -176,15 +191,21 @@ private Object parseObject(
}
}

// leniently assume it is am array/List (previously only the simple JSON parser made this assumption)
SerIterable parseUnknownArray(Class<?> declaredType) {
throw new IllegalArgumentException("JSON contained an array without information about the Java type");
if (declaredType.isArray()) {
return SerIteratorFactory.array(declaredType.getComponentType());
} else {
return SerIteratorFactory.list(Object.class, Collections.emptyList());
}
}

// leniently assume it is a Map (previously only the simple JSON parser made this assumption)
SerIterable parseUnknownObject(Class<?> declaredType) {
throw new IllegalArgumentException("JSON contained an object without information about the Java type");
return SerIteratorFactory.map(String.class, Object.class, Collections.emptyList());
}

private Object parseTypedBean(Class<?> declaredType, boolean rootType) throws Exception {
private Object parseTypedBean(Class<?> declaredType, boolean rootType) throws IOException, ClassNotFoundException {
var typeStr = input.acceptString();
Class<?> effectiveType = SerTypeMapper.decodeType(typeStr, settings, basePackage, knownTypes);
if (rootType) {
Expand All @@ -204,12 +225,14 @@ private Object parseTypedBean(Class<?> declaredType, boolean rootType) throws Ex
return parseBean(event, effectiveType);
}

private Object parseTypedSimple(Class<?> declaredType) throws Exception {
private Object parseTypedSimple(Class<?> declaredType) throws IOException, ClassNotFoundException {
var typeStr = input.acceptString();
var effectiveType = settings.getDeserializers().decodeType(typeStr, settings, basePackage, knownTypes, declaredType);
if (!declaredType.isAssignableFrom(effectiveType)) {
throw new IllegalArgumentException("Specified type is incompatible with declared type: " +
declaredType.getName() + " and " + effectiveType.getName());
if (!declaredType.isPrimitive() || ResolvedType.of(declaredType).toBoxed().getRawType() != effectiveType) {
throw new IllegalArgumentException("Specified type is incompatible with declared type: " +
declaredType.getName() + " and " + effectiveType.getName());
}
}
input.acceptEvent(JsonEvent.COMMA);
var valueKey = input.acceptObjectKey(input.readEvent());
Expand All @@ -221,7 +244,7 @@ private Object parseTypedSimple(Class<?> declaredType) throws Exception {
return result;
}

private Object parseTypedMeta() throws Exception {
private Object parseTypedMeta() throws IOException, ClassNotFoundException {
var metaType = input.acceptString();
var childIterable = settings.getIteratorFactory().createIterable(metaType, settings, knownTypes);
input.acceptEvent(JsonEvent.COMMA);
Expand All @@ -234,7 +257,7 @@ private Object parseTypedMeta() throws Exception {
return result;
}

private Object parseIterable(JsonEvent event, SerIterable iterable) throws Exception {
private Object parseIterable(JsonEvent event, SerIterable iterable) throws IOException, ClassNotFoundException {
if (iterable.category() == SerCategory.MAP) {
return parseIterableMap(event, iterable);
} else if (iterable.category() == SerCategory.COUNTED) {
Expand All @@ -248,7 +271,7 @@ private Object parseIterable(JsonEvent event, SerIterable iterable) throws Excep
}
}

private Object parseIterableMap(JsonEvent event, SerIterable iterable) throws Exception {
private Object parseIterableMap(JsonEvent event, SerIterable iterable) throws IOException, ClassNotFoundException {
if (event == JsonEvent.OBJECT) {
event = input.readEvent();
while (event != JsonEvent.OBJECT_END) {
Expand Down Expand Up @@ -276,7 +299,7 @@ private Object parseIterableMap(JsonEvent event, SerIterable iterable) throws Ex
return iterable.build();
}

private Object parseIterableTable(JsonEvent event, SerIterable iterable) throws Exception {
private Object parseIterableTable(JsonEvent event, SerIterable iterable) throws IOException, ClassNotFoundException {
input.ensureEvent(event, JsonEvent.ARRAY);
event = input.readEvent();
while (event != JsonEvent.ARRAY_END) {
Expand All @@ -293,7 +316,7 @@ private Object parseIterableTable(JsonEvent event, SerIterable iterable) throws
return iterable.build();
}

private Object parseIterableGrid(JsonEvent event, SerIterable iterable) throws Exception {
private Object parseIterableGrid(JsonEvent event, SerIterable iterable) throws IOException, ClassNotFoundException {
input.ensureEvent(event, JsonEvent.ARRAY);
input.acceptEvent(JsonEvent.NUMBER_INTEGRAL);
var rows = (int) input.parseNumberIntegral();
Expand All @@ -318,7 +341,7 @@ private Object parseIterableGrid(JsonEvent event, SerIterable iterable) throws E
return iterable.build();
}

private Object parseIterableCounted(JsonEvent event, SerIterable iterable) throws Exception {
private Object parseIterableCounted(JsonEvent event, SerIterable iterable) throws IOException, ClassNotFoundException {
input.ensureEvent(event, JsonEvent.ARRAY);
event = input.readEvent();
while (event != JsonEvent.ARRAY_END) {
Expand All @@ -333,7 +356,7 @@ private Object parseIterableCounted(JsonEvent event, SerIterable iterable) throw
return iterable.build();
}

private Object parseIterableArray(JsonEvent event, SerIterable iterable) throws Exception {
private Object parseIterableArray(JsonEvent event, SerIterable iterable) throws IOException, ClassNotFoundException {
input.ensureEvent(event, JsonEvent.ARRAY);
event = input.readEvent();
while (event != JsonEvent.ARRAY_END) {
Expand All @@ -344,7 +367,7 @@ private Object parseIterableArray(JsonEvent event, SerIterable iterable) throws
return iterable.build();
}

private Object parseSimple(JsonEvent event, Class<?> type) throws Exception {
private Object parseSimple(JsonEvent event, Class<?> type) throws IOException {
switch (event) {
case STRING: {
var text = input.parseString();
Expand Down
21 changes: 13 additions & 8 deletions src/main/java/org/joda/beans/ser/json/JodaBeanJsonReader.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

import java.io.Reader;
import java.io.StringReader;
import java.io.UncheckedIOException;

import org.joda.beans.Bean;
import org.joda.beans.JodaBeanUtils;
Expand Down Expand Up @@ -47,6 +48,8 @@ public JodaBeanJsonReader(JodaBeanSer settings) {
*
* @param input the input string, not null
* @return the bean, not null
* @throws UncheckedIOException if unable to read the stream
* @throws IllegalArgumentException if unable to parse the JSON
*/
public Bean read(String input) {
return read(input, Bean.class);
Expand All @@ -59,6 +62,9 @@ public Bean read(String input) {
* @param input the input string, not null
* @param rootType the root type, not null
* @return the bean, not null
* @throws UncheckedIOException if unable to read the stream
* @throws IllegalArgumentException if unable to parse the JSON
* @throws ClassCastException if the root type does not match the type in the JSON
*/
public <T> T read(String input, Class<T> rootType) {
JodaBeanUtils.notNull(input, "input");
Expand All @@ -70,6 +76,8 @@ public <T> T read(String input, Class<T> rootType) {
*
* @param input the input reader, not null
* @return the bean, not null
* @throws UncheckedIOException if unable to read the stream
* @throws IllegalArgumentException if unable to parse the JSON
*/
public Bean read(Reader input) {
return read(input, Bean.class);
Expand All @@ -82,18 +90,15 @@ public Bean read(Reader input) {
* @param input the input reader, not null
* @param rootType the root type, not null
* @return the bean, not null
* @throws UncheckedIOException if unable to read the stream
* @throws IllegalArgumentException if unable to parse the JSON
* @throws ClassCastException if the root type does not match the type in the JSON
*/
public <T> T read(Reader input, Class<T> rootType) {
JodaBeanUtils.notNull(input, "input");
JodaBeanUtils.notNull(rootType, "rootType");
try {
var jsonInput = new JsonInput(input);
return parseRoot(jsonInput, rootType);
} catch (RuntimeException ex) {
throw ex;
} catch (Exception ex) {
throw new RuntimeException(ex);
}
var jsonInput = new JsonInput(input);
return parseRoot(jsonInput, rootType);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,10 @@

import java.io.Reader;
import java.io.StringReader;
import java.util.Collections;
import java.io.UncheckedIOException;

import org.joda.beans.JodaBeanUtils;
import org.joda.beans.ser.JodaBeanSer;
import org.joda.beans.ser.SerIterable;
import org.joda.beans.ser.SerIteratorFactory;

/**
* Provides the ability for a Joda-Bean to read from JSON.
Expand Down Expand Up @@ -51,6 +49,8 @@ public JodaBeanSimpleJsonReader(JodaBeanSer settings) {
* @param input the input string, not null
* @param rootType the root type, not null
* @return the bean, not null
* @throws UncheckedIOException if unable to read the stream
* @throws IllegalArgumentException if unable to parse the JSON
*/
public <T> T read(String input, Class<T> rootType) {
JodaBeanUtils.notNull(input, "input");
Expand All @@ -64,33 +64,14 @@ public <T> T read(String input, Class<T> rootType) {
* @param input the input reader, not null
* @param rootType the root type, not null
* @return the bean, not null
* @throws UncheckedIOException if unable to read the stream
* @throws IllegalArgumentException if unable to parse the JSON
*/
public <T> T read(Reader input, Class<T> rootType) {
JodaBeanUtils.notNull(input, "input");
JodaBeanUtils.notNull(rootType, "rootType");
try {
var jsonInput = new JsonInput(input);
return parseRoot(jsonInput, rootType);
} catch (RuntimeException ex) {
throw ex;
} catch (Exception ex) {
throw new RuntimeException(ex);
}
}

//-----------------------------------------------------------------------
@Override
SerIterable parseUnknownArray(Class<?> declaredType) {
if (declaredType.isArray()) {
return SerIteratorFactory.array(declaredType.getComponentType());
} else {
return SerIteratorFactory.list(Object.class, Collections.emptyList());
}
}

@Override
SerIterable parseUnknownObject(Class<?> declaredType) {
return SerIteratorFactory.map(String.class, Object.class, Collections.emptyList());
var jsonInput = new JsonInput(input);
return parseRoot(jsonInput, rootType);
}

}
3 changes: 2 additions & 1 deletion src/test/java/org/joda/beans/ser/json/TestSerializeJson.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
package org.joda.beans.ser.json;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.assertj.core.api.Assertions.offset;

Expand Down Expand Up @@ -452,7 +453,7 @@ void test_read_rootTypeInvalid() {

@Test
void test_read_rootTypeArgumentInvalid() {
assertThatIllegalArgumentException()
assertThatExceptionOfType(ClassCastException.class)
.isThrownBy(() -> JodaBeanSer.COMPACT.jsonReader().read("{}", Integer.class));
}

Expand Down

0 comments on commit 6a052ed

Please sign in to comment.