Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

More lenient JSON handling #452

Merged
merged 1 commit into from
Dec 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 44 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,23 @@ 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
*/
<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 | ClassCastException 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 +119,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 +134,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 +190,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 +224,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 +243,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 +256,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 +270,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 +298,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 +315,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 +340,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 +355,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 +366,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
19 changes: 11 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,8 @@ 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
*/
public <T> T read(String input, Class<T> rootType) {
JodaBeanUtils.notNull(input, "input");
Expand All @@ -70,6 +75,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 +89,14 @@ 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
*/
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);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
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.assertThatNoException;
import static org.assertj.core.api.Assertions.offset;
Expand Down Expand Up @@ -207,7 +206,7 @@ void test_read_emptyFlexiBean() {

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

Expand Down
Loading