Fix: toJson must use setIntercept if target bean is passed

This commit is contained in:
Roland Praml
2022-07-28 15:07:05 +02:00
parent 3c2b5f253f
commit dbcc779731
7 changed files with 138 additions and 26 deletions
@@ -33,4 +33,6 @@ public interface SpiJsonReader {
void beanVisitor(Object bean, Map<String, Object> unmappedProperties);
Object readValueUsingObjectMapper(Class<?> propertyType) throws IOException;
boolean isIntercept();
}
@@ -1414,7 +1414,11 @@ public class BeanProperty implements ElPropertyValue, Property, STreeProperty {
JsonToken event = ctx.nextToken();
if (JsonToken.VALUE_NULL == event) {
if (jsonDeserialize) {
setValue(bean, null);
if (ctx.isIntercept()) {
setValueIntercept(bean, null);
} else {
setValue(bean, null);
}
}
} else {
// expect to read non-null json value
@@ -1433,7 +1437,11 @@ public class BeanProperty implements ElPropertyValue, Property, STreeProperty {
}
}
if (jsonDeserialize) {
setValue(bean, objValue);
if (ctx.isIntercept()) {
setValueIntercept(bean, objValue);
} else {
setValue(bean, objValue);
}
}
}
}
@@ -50,7 +50,11 @@ class BeanPropertyAssocManyJsonHelp {
if (JsonToken.START_ARRAY != event && JsonToken.START_OBJECT != event) {
throw new JsonParseException(parser, "Unexpected token " + event + " - expecting start array or object");
}
many.setValue(parentBean, many.jsonReadCollection(readJson, parentBean));
if (readJson.isIntercept()) {
many.setValueIntercept(parentBean, many.jsonReadCollection(readJson, parentBean));
} else {
many.setValue(parentBean, many.jsonReadCollection(readJson, parentBean));
}
}
/**
@@ -785,7 +785,11 @@ public class BeanPropertyAssocOne<T> extends BeanPropertyAssoc<T> implements STr
if (jsonDeserialize && targetDescriptor != null) {
T target = (T) value(bean);
T assocBean = targetDescriptor.jsonRead(readJson, name, target);
setValue(bean, assocBean);
if (readJson.isIntercept()) {
setValueIntercept(bean, assocBean);
} else {
setValue(bean, assocBean);
}
}
}
@@ -125,7 +125,7 @@ public final class DJsonContext implements SpiJsonContext {
BeanDescriptor<T> desc = getDescriptor(cls);
try {
return desc.jsonRead(new ReadJson(desc, parser, options, determineObjectMapper(options)), null, null);
return desc.jsonRead(new ReadJson(desc, parser, options, determineObjectMapper(options), false), null, null);
} catch (IOException e) {
throw new JsonIOException(e);
}
@@ -158,10 +158,10 @@ public final class DJsonContext implements SpiJsonContext {
@Override
public <T> void toBean(T target, JsonParser parser, JsonReadOptions options) throws JsonIOException {
BeanDescriptor<T> desc = (BeanDescriptor<T>) getDescriptor(target.getClass());
try {
desc.jsonRead(new ReadJson(desc, parser, options, determineObjectMapper(options)), null, target);
desc.jsonRead(new ReadJson(desc, parser, options, determineObjectMapper(options), target != null), null, target);
} catch (IOException e) {
throw new JsonIOException(e);
}
@@ -171,14 +171,14 @@ public final class DJsonContext implements SpiJsonContext {
public <T> DJsonBeanReader<T> createBeanReader(Class<T> cls, JsonParser parser, JsonReadOptions options) throws JsonIOException {
BeanDescriptor<T> desc = getDescriptor(cls);
return new DJsonBeanReader<>(desc, new ReadJson(desc, parser, options, determineObjectMapper(options)));
return new DJsonBeanReader<>(desc, new ReadJson(desc, parser, options, determineObjectMapper(options), false));
}
@Override
public <T> DJsonBeanReader<T> createBeanReader(BeanType<T> beanType, JsonParser parser, JsonReadOptions options) throws JsonIOException {
BeanDescriptor<T> desc = (BeanDescriptor<T>) beanType;
SpiJsonReader readJson = new ReadJson(desc, parser, options, determineObjectMapper(options));
SpiJsonReader readJson = new ReadJson(desc, parser, options, determineObjectMapper(options), false);
return new DJsonBeanReader<>(desc, readJson);
}
@@ -211,7 +211,7 @@ public final class DJsonContext implements SpiJsonContext {
public <T> List<T> toList(Class<T> cls, JsonParser src, JsonReadOptions options) throws JsonIOException {
BeanDescriptor<T> desc = getDescriptor(cls);
SpiJsonReader readJson = new ReadJson(desc, src, options, determineObjectMapper(options));
SpiJsonReader readJson = new ReadJson(desc, src, options, determineObjectMapper(options), false);
try {
JsonToken currentToken = src.getCurrentToken();
@@ -225,7 +225,7 @@ public final class DJsonContext implements SpiJsonContext {
List<T> list = new ArrayList<>();
do {
// CHECKME: Should we update the list
T bean = desc.jsonRead(readJson, null, null);
T bean = desc.jsonRead(readJson, null, null);
if (bean == null) {
break;
} else {
@@ -395,7 +395,7 @@ public final class DJsonContext implements SpiJsonContext {
BeanDescriptor<?> desc = (BeanDescriptor<?>) beanType;
JsonParser parser = createParser(new StringReader(json));
return new ReadJson(desc, parser, null, defaultObjectMapper);
return new ReadJson(desc, parser, null, defaultObjectMapper, false);
}
@Override
@@ -32,11 +32,12 @@ public final class ReadJson implements SpiJsonReader {
private final Object objectMapper;
private final PersistenceContext persistenceContext;
private final LoadContext loadContext;
private final boolean intercept;
/**
* Construct with parser and readOptions.
*/
public ReadJson(BeanDescriptor<?> desc, JsonParser parser, JsonReadOptions readOptions, Object objectMapper) {
public ReadJson(BeanDescriptor<?> desc, JsonParser parser, JsonReadOptions readOptions, Object objectMapper, boolean intercept) {
this.rootDesc = desc;
this.parser = parser;
this.objectMapper = objectMapper;
@@ -45,6 +46,7 @@ public final class ReadJson implements SpiJsonReader {
// only create visitorMap, pathStack if needed ...
this.visitorMap = (readOptions == null) ? null : readOptions.getVisitorMap();
this.pathStack = (visitorMap == null && loadContext == null) ? null : new PathStack();
this.intercept = intercept;
}
/**
@@ -58,6 +60,7 @@ public final class ReadJson implements SpiJsonReader {
this.objectMapper = source.objectMapper;
this.persistenceContext = source.persistenceContext;
this.loadContext = source.loadContext;
this.intercept = source.intercept;
}
private LoadContext initLoadContext(BeanDescriptor<?> desc, JsonReadOptions readOptions) {
@@ -203,4 +206,11 @@ public final class ReadJson implements SpiJsonReader {
return getObjectMapper().readValue(parser, propertyType);
}
/**
* Do we have to set values via intercept or not.
*/
@Override
public boolean isIntercept() {
return intercept;
}
}
@@ -1,58 +1,142 @@
package org.tests.text.json;
import com.fasterxml.jackson.core.JsonParser;
import io.ebean.xtest.BaseTestCase;
import io.ebean.BeanState;
import io.ebean.DB;
import io.ebean.xtest.BaseTestCase;
import io.ebeaninternal.api.SpiEbeanServer;
import io.ebeaninternal.api.json.SpiJsonReader;
import io.ebeaninternal.server.deploy.BeanDescriptor;
import io.ebeaninternal.server.text.json.ReadJson;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.tests.model.basic.Address;
import org.tests.model.basic.Customer;
import java.io.IOException;
import java.io.StringReader;
import java.util.Set;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.*;
public class TestJsonBeanDescriptorParse extends BaseTestCase {
@Test
public void test() throws IOException {
SpiEbeanServer server = (SpiEbeanServer) DB.getDefault();
@BeforeEach
void setup() {
Customer customer = new Customer();
customer.setName("Hello Roland");
customer.setId(234);
Address address = new Address();
address.setLine1("foo");
DB.save(address);
customer.setBillingAddress(address);
DB.save(customer);
}
@AfterEach
void teardown() {
DB.delete(Customer.class, 234);
}
@Test
public void testJsonRead() throws IOException {
SpiEbeanServer server = (SpiEbeanServer) DB.getDefault();
BeanDescriptor<Customer> descriptor = server.descriptor(Customer.class);
SpiJsonReader readJson = createRead(server, descriptor);
Customer customer = descriptor.jsonRead(readJson, null, null);
assertEquals(Integer.valueOf(123), customer.getId());
assertEquals("Hello rob", customer.getName());
assertEquals("Hello Rob", customer.getName());
BeanState beanState = DB.beanState(customer);
Set<String> loadedProps = beanState.loadedProps();
assertEquals(2, loadedProps.size());
assertTrue(loadedProps.contains("id"));
assertTrue(loadedProps.contains("name"));
assertThat(beanState.dirtyValues()).isEmpty();
}
@Test
public void testJsonUpdate() throws IOException {
Customer customer = DB.find(Customer.class, 234);
DB.json().toBean(customer, "{}");
assertFalse(DB.beanState(customer).isDirty());
assertEquals("Hello Roland", customer.getName());
DB.json().toBean(customer, "{\"name\":\"Hello Roland\"}");
assertFalse(DB.beanState(customer).isDirty());
DB.json().toBean(customer, "{\"name\":\"Hello Rob\"}");
assertEquals("Hello Rob", customer.getName());
assertThat(DB.beanState(customer).changedProps()).containsExactly("name");
assertTrue(DB.beanState(customer).isDirty());
DB.json().toBean(customer, "{\"name\":null}");
assertEquals(null, customer.getName());
assertEquals("foo", customer.getBillingAddress().getLine1());
DB.json().toBean(customer, "{\"billingAddress\":{\"line1\":\"foo\"}}");
assertFalse(DB.beanState(customer.getBillingAddress()).isDirty());
DB.json().toBean(customer, "{\"billingAddress\":{\"line1\":\"bar\"}}");
assertEquals("bar", customer.getBillingAddress().getLine1());
assertTrue(DB.beanState(customer.getBillingAddress()).isDirty());
DB.json().toBean(customer, "{\"contacts\":[{\"firstName\":\"Noemi\"},{\"firstName\":\"Roland\"}]}");
assertThat(customer.getContacts()).hasSize(2);
// must not change contacts
DB.json().toBean(customer, "{}");
assertThat(customer.getContacts()).hasSize(2);
DB.json().toBean(customer, "{\"contacts\":[]}");
assertThat(customer.getContacts()).hasSize(0);
/*
customer.setName("Hello Roland");
customer.setId(234);
Address address = new Address();
address.setLine1("foo");
DB.save(address);
customer.setBillingAddress(address);
DB.save(customer);
readJson = createRead(server, descriptor);
descriptor.jsonRead(readJson, null, customer);
assertEquals(Integer.valueOf(123), customer.getId());
assertEquals("Hello rob", customer.getName());
assertEquals("Hello Rob", customer.getName());
assertEquals(123, customer.getId());
// now check, if toJson is routed over intercept for existing models
customer = DB.find(Customer.class, 234);
DB.json().toBean(customer, "{}");
assertFalse(DB.beanState(customer).isDirty());
assertEquals("Hello Roland", customer.getName());
DB.json().toBean(customer, "{\"name\":\"Hello Roland\"}");
assertFalse(DB.beanState(customer).isDirty());
DB.json().toBean(customer, "{\"name\":\"Hello Rob\"}");
assertEquals("Hello Roland", customer.getName());
assertThat(DB.beanState(customer).changedProps()).containsExactly("name");
assertTrue(DB.beanState(customer).isDirty());
DB.json().toBean(customer, "{\"name\":null}");
assertEquals(null, customer.getName());
assertEquals("foo", customer.getBillingAddress().getLine2());
DB.json().toBean(customer, "{\"billingAdress\":{\"line2\":\"foo\"}}");
assertEquals("foo", customer.getBillingAddress().getLine2());
DB.delete(customer); // cleanup*/
}
private SpiJsonReader createRead(SpiEbeanServer server, BeanDescriptor<Customer> descriptor) {
StringReader reader = new StringReader("{\"id\":123,\"name\":\"Hello rob\"}");
StringReader reader = new StringReader("{\"id\":123,\"name\":\"Hello Rob\"}");
JsonParser parser = server.json().createParser(reader);
SpiJsonReader readJson = new ReadJson(descriptor, parser, null, null);
SpiJsonReader readJson = new ReadJson(descriptor, parser, null, null, false);
return readJson;
}