mirror of
https://github.com/ebean-orm/ebean.git
synced 2026-10-23 21:15:52 +00:00
#1839 - Fix JSON marshalling of @ElementCollection maps and scalar
This commit is contained in:
@@ -62,6 +62,16 @@ public interface SpiJsonWriter extends JsonWriter {
|
||||
*/
|
||||
void endAssocMany();
|
||||
|
||||
/**
|
||||
* Start a Many.
|
||||
*/
|
||||
void beginAssocManyMap(String name, boolean elementCollection);
|
||||
|
||||
/**
|
||||
* End a Many.
|
||||
*/
|
||||
void endAssocManyMap(boolean elementCollection);
|
||||
|
||||
/**
|
||||
* Write value using underlying Jaskson object mapper if available.
|
||||
*/
|
||||
|
||||
@@ -3549,6 +3549,10 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
|
||||
throw new IllegalStateException("Unexpected - expect Element override");
|
||||
}
|
||||
|
||||
public boolean isJsonReadCollection() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public void jsonWrite(SpiJsonWriter writeJson, EntityBean bean) throws IOException {
|
||||
jsonHelp.jsonWrite(writeJson, bean, null);
|
||||
}
|
||||
|
||||
@@ -21,6 +21,11 @@ abstract class BeanDescriptorElement<T> extends BeanDescriptor<T> {
|
||||
this.elementHelp = elementHelp;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isJsonReadCollection() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find and return the first base scalar type (and we expect only 1).
|
||||
*/
|
||||
|
||||
@@ -35,6 +35,11 @@ class BeanDescriptorElementEmbedded<T> extends BeanDescriptorElement<T> {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isJsonReadCollection() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialiseOther(BeanDescriptorInitContext initContext) {
|
||||
super.initialiseOther(initContext);
|
||||
|
||||
@@ -28,10 +28,14 @@ class BeanDescriptorElementEmbeddedMap<T> extends BeanDescriptorElementEmbedded<
|
||||
this.stringKey = String.class.equals(scalarTypeKey.getType());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isJsonReadCollection() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public void jsonWriteMapEntry(SpiJsonWriter ctx, Map.Entry<?, ?> entry) throws IOException {
|
||||
ctx.writeStartObject();
|
||||
if (stringKey) {
|
||||
Object key = entry.getKey();
|
||||
String keyName = (key == null) ? "null" : key.toString();
|
||||
@@ -43,7 +47,6 @@ class BeanDescriptorElementEmbeddedMap<T> extends BeanDescriptorElementEmbedded<
|
||||
ctx.writeFieldName("value");
|
||||
writeJsonElement(ctx, entry.getValue());
|
||||
}
|
||||
ctx.writeEndObject();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -52,30 +55,21 @@ class BeanDescriptorElementEmbeddedMap<T> extends BeanDescriptorElementEmbedded<
|
||||
JsonParser parser = readJson.getParser();
|
||||
ElementCollector add = elementHelp.createCollector();
|
||||
do {
|
||||
JsonToken token = parser.nextToken();
|
||||
if (token != JsonToken.START_OBJECT) {
|
||||
String fieldName = parser.nextFieldName();
|
||||
if (fieldName == null) {
|
||||
break;
|
||||
}
|
||||
if (stringKey) {
|
||||
String key = parser.nextFieldName();
|
||||
parser.nextToken();
|
||||
Object val = readJsonElement(readJson, null);
|
||||
add.addKeyValue(key, val);
|
||||
|
||||
add.addKeyValue(fieldName, val);
|
||||
} else {
|
||||
parser.nextFieldName();
|
||||
Object key = scalarTypeKey.jsonRead(parser);
|
||||
|
||||
parser.nextFieldName();
|
||||
Object val = readJsonElement(readJson, null);
|
||||
add.addKeyValue(key, val);
|
||||
}
|
||||
|
||||
token = parser.nextToken();
|
||||
if (token != JsonToken.END_OBJECT) {
|
||||
break;
|
||||
}
|
||||
|
||||
} while (true);
|
||||
|
||||
return add.collection();
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package io.ebeaninternal.server.deploy;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonParser;
|
||||
import com.fasterxml.jackson.core.JsonToken;
|
||||
import io.ebean.bean.EntityBean;
|
||||
import io.ebeaninternal.api.json.SpiJsonReader;
|
||||
import io.ebeaninternal.api.json.SpiJsonWriter;
|
||||
@@ -35,7 +34,6 @@ class BeanDescriptorElementScalarMap<T> extends BeanDescriptorElement<T> {
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public void jsonWriteMapEntry(SpiJsonWriter ctx, Map.Entry<?, ?> entry) throws IOException {
|
||||
ctx.writeStartObject();
|
||||
if (stringKey) {
|
||||
Object key = entry.getKey();
|
||||
String keyName = (key == null) ? "null" : key.toString();
|
||||
@@ -47,7 +45,6 @@ class BeanDescriptorElementScalarMap<T> extends BeanDescriptorElement<T> {
|
||||
ctx.writeFieldName("value");
|
||||
scalarTypeVal.jsonWrite(ctx.gen(), entry.getValue());
|
||||
}
|
||||
ctx.writeEndObject();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -56,30 +53,21 @@ class BeanDescriptorElementScalarMap<T> extends BeanDescriptorElement<T> {
|
||||
JsonParser parser = readJson.getParser();
|
||||
ElementCollector add = elementHelp.createCollector();
|
||||
do {
|
||||
JsonToken token = parser.nextToken();
|
||||
if (token != JsonToken.START_OBJECT) {
|
||||
String fieldName = parser.nextFieldName();
|
||||
if (fieldName == null) {
|
||||
break;
|
||||
}
|
||||
if (stringKey) {
|
||||
String key = parser.nextFieldName();
|
||||
parser.nextToken();
|
||||
Object val = scalarTypeVal.jsonRead(parser);
|
||||
add.addKeyValue(key, val);
|
||||
|
||||
add.addKeyValue(fieldName, val);
|
||||
} else {
|
||||
parser.nextFieldName();
|
||||
Object key = scalarTypeKey.jsonRead(parser);
|
||||
|
||||
parser.nextFieldName();
|
||||
Object val = scalarTypeVal.jsonRead(parser);
|
||||
add.addKeyValue(key, val);
|
||||
}
|
||||
|
||||
token = parser.nextToken();
|
||||
if (token != JsonToken.END_OBJECT) {
|
||||
break;
|
||||
}
|
||||
|
||||
} while (true);
|
||||
|
||||
return add.collection();
|
||||
|
||||
@@ -163,11 +163,12 @@ public class BeanMapHelp<T> extends BaseCollectionHelp<T> {
|
||||
}
|
||||
|
||||
if (!map.isEmpty() || ctx.isIncludeEmpty()) {
|
||||
ctx.beginAssocMany(name);
|
||||
final boolean elementCollection = many.isElementCollection();
|
||||
ctx.beginAssocManyMap(name, elementCollection);
|
||||
for (Entry<?, ?> entry : map.entrySet()) {
|
||||
many.jsonWriteMapEntry(ctx, entry);
|
||||
}
|
||||
ctx.endAssocMany();
|
||||
ctx.endAssocManyMap(elementCollection);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -971,7 +971,11 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> implements ST
|
||||
}
|
||||
|
||||
void jsonWriteMapEntry(SpiJsonWriter ctx, Map.Entry<?, ?> entry) throws IOException {
|
||||
elementDescriptor.jsonWriteMapEntry(ctx, entry);
|
||||
if (elementDescriptor != null) {
|
||||
elementDescriptor.jsonWriteMapEntry(ctx, entry);
|
||||
} else {
|
||||
targetDescriptor.jsonWrite(ctx, (EntityBean)entry.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
void jsonWriteElementValue(SpiJsonWriter ctx, Object element) {
|
||||
@@ -1051,10 +1055,9 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> implements ST
|
||||
*/
|
||||
public Object jsonReadCollection(SpiJsonReader readJson, EntityBean parentBean) throws IOException {
|
||||
|
||||
if (elementDescriptor != null && manyType.isMap()) {
|
||||
if (elementDescriptor != null && elementDescriptor.isJsonReadCollection()) {
|
||||
return elementDescriptor.jsonReadCollection(readJson, parentBean);
|
||||
}
|
||||
|
||||
BeanCollection<?> collection = createEmpty(parentBean);
|
||||
BeanCollectionAdd add = getBeanCollectionAdd(collection);
|
||||
do {
|
||||
|
||||
@@ -40,22 +40,18 @@ class BeanPropertyAssocManyJsonHelp {
|
||||
if (!this.many.jsonDeserialize) {
|
||||
return;
|
||||
}
|
||||
|
||||
JsonParser parser = readJson.getParser();
|
||||
JsonToken event = parser.nextToken();
|
||||
if (JsonToken.VALUE_NULL == event) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (many.isTransient()) {
|
||||
jsonReadTransientUsingObjectMapper(readJson, parentBean);
|
||||
return;
|
||||
}
|
||||
|
||||
if (JsonToken.START_ARRAY != event) {
|
||||
throw new JsonParseException(parser, "Unexpected token " + event + " - expecting start_array");
|
||||
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));
|
||||
}
|
||||
|
||||
|
||||
@@ -398,6 +398,37 @@ public class WriteJson implements SpiJsonWriter {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beginAssocManyMap(String key, boolean elementCollection) {
|
||||
try {
|
||||
pathStack.pushPathKey(key);
|
||||
if (key != null) {
|
||||
generator.writeFieldName(key);
|
||||
}
|
||||
if (elementCollection) {
|
||||
generator.writeStartObject();
|
||||
} else {
|
||||
generator.writeStartArray();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new JsonIOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void endAssocManyMap(boolean elementCollection) {
|
||||
try {
|
||||
pathStack.pop();
|
||||
if (elementCollection) {
|
||||
generator.writeEndObject();
|
||||
} else {
|
||||
generator.writeEndArray();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new JsonIOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> void writeBean(BeanDescriptor<T> desc, EntityBean bean) {
|
||||
createWriteBean(desc, bean).write(this);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package org.tests.model.elementcollection;
|
||||
|
||||
import io.ebean.BaseTestCase;
|
||||
import io.ebean.DB;
|
||||
import io.ebean.Ebean;
|
||||
import org.ebeantest.LoggedSqlCollector;
|
||||
import org.junit.Test;
|
||||
@@ -175,4 +176,21 @@ public class TestElementCollectionBasicMap extends BaseTestCase {
|
||||
assertThat(fromJson.getPhoneNumbers()).containsValues("021 1234", "021 4321");
|
||||
assertThat(fromJson.getPhoneNumbers().get("home")).isEqualTo("021 1234");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void json() {
|
||||
|
||||
EcmPerson person = new EcmPerson("Fiona021");
|
||||
person.getPhoneNumbers().put("home", "021 1234");
|
||||
person.getPhoneNumbers().put("work", "021 4321");
|
||||
|
||||
final String asJson = DB.json().toJson(person);
|
||||
|
||||
assertThat(asJson).isEqualTo("{\"name\":\"Fiona021\",\"phoneNumbers\":{\"home\":\"021 1234\",\"work\":\"021 4321\"}}");
|
||||
|
||||
final EcmPerson fromJson = DB.json().toBean(EcmPerson.class, asJson);
|
||||
assertThat(fromJson.getName()).isEqualTo("Fiona021");
|
||||
assertThat(fromJson.getPhoneNumbers()).hasSize(2);
|
||||
assertThat(fromJson.getPhoneNumbers().toString()).isEqualTo("BeanMap size[2] map{home=021 1234, work=021 4321}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package org.tests.model.elementcollection;
|
||||
|
||||
import io.ebean.BaseTestCase;
|
||||
import io.ebean.DB;
|
||||
import io.ebean.Ebean;
|
||||
import org.ebeantest.LoggedSqlCollector;
|
||||
import org.junit.Test;
|
||||
@@ -170,4 +171,21 @@ public class TestElementCollectionBasicSet extends BaseTestCase {
|
||||
EcsPerson fromJson = Ebean.json().toBean(EcsPerson.class, asJson);
|
||||
assertThat(fromJson.getPhoneNumbers()).containsAll(foundFirst.getPhoneNumbers());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void json() {
|
||||
|
||||
EcsPerson person = new EcsPerson("Fiona021");
|
||||
person.getPhoneNumbers().add("021 1234");
|
||||
person.getPhoneNumbers().add("021 4321");
|
||||
|
||||
final String asJson = DB.json().toJson(person);
|
||||
|
||||
assertThat(asJson).isEqualTo("{\"name\":\"Fiona021\",\"phoneNumbers\":[\"021 1234\",\"021 4321\"]}");
|
||||
|
||||
final EcsPerson fromJson = DB.json().toBean(EcsPerson.class, asJson);
|
||||
assertThat(fromJson.getName()).isEqualTo("Fiona021");
|
||||
assertThat(fromJson.getPhoneNumbers()).hasSize(2);
|
||||
assertThat(fromJson.getPhoneNumbers().toString()).isEqualTo("BeanSet size[2] set[021 1234, 021 4321]");
|
||||
}
|
||||
}
|
||||
|
||||
+22
-4
@@ -1,6 +1,7 @@
|
||||
package org.tests.model.elementcollection;
|
||||
|
||||
import io.ebean.BaseTestCase;
|
||||
import io.ebean.DB;
|
||||
import io.ebean.Ebean;
|
||||
import org.ebeantest.LoggedSqlCollector;
|
||||
import org.junit.Test;
|
||||
@@ -17,8 +18,8 @@ public class TestElementCollectionEmbeddedList extends BaseTestCase {
|
||||
LoggedSqlCollector.start();
|
||||
|
||||
EcblPerson person = new EcblPerson("Fiona64021");
|
||||
person.getPhoneNumbers().add(new EcPhone("64", "021","1234"));
|
||||
person.getPhoneNumbers().add(new EcPhone("64","021","4321"));
|
||||
person.getPhoneNumbers().add(new EcPhone("64", "021", "1234"));
|
||||
person.getPhoneNumbers().add(new EcPhone("64", "021", "4321"));
|
||||
Ebean.save(person);
|
||||
|
||||
List<String> sql = LoggedSqlCollector.current();
|
||||
@@ -35,8 +36,8 @@ public class TestElementCollectionEmbeddedList extends BaseTestCase {
|
||||
}
|
||||
|
||||
EcblPerson person1 = new EcblPerson("Fiona6409");
|
||||
person1.getPhoneNumbers().add(new EcPhone("61","09","1234"));
|
||||
person1.getPhoneNumbers().add(new EcPhone("64","09","4321"));
|
||||
person1.getPhoneNumbers().add(new EcPhone("61", "09", "1234"));
|
||||
person1.getPhoneNumbers().add(new EcPhone("64", "09", "4321"));
|
||||
Ebean.save(person1);
|
||||
|
||||
LoggedSqlCollector.current();
|
||||
@@ -175,4 +176,21 @@ public class TestElementCollectionEmbeddedList extends BaseTestCase {
|
||||
assertThat(phoneString).contains("64-021-1234");
|
||||
assertThat(phoneString).contains("64-021-4321");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void json() {
|
||||
|
||||
EcblPerson person = new EcblPerson("Fiona64021");
|
||||
person.getPhoneNumbers().add(new EcPhone("64", "021", "1234"));
|
||||
person.getPhoneNumbers().add(new EcPhone("64", "021", "4321"));
|
||||
|
||||
final String asJson = DB.json().toJson(person);
|
||||
|
||||
assertThat(asJson).isEqualTo("{\"name\":\"Fiona64021\",\"phoneNumbers\":[{\"countryCode\":\"64\",\"area\":\"021\",\"number\":\"1234\"},{\"countryCode\":\"64\",\"area\":\"021\",\"number\":\"4321\"}]}");
|
||||
|
||||
final EcblPerson fromJson = DB.json().toBean(EcblPerson.class, asJson);
|
||||
assertThat(fromJson.getName()).isEqualTo("Fiona64021");
|
||||
assertThat(fromJson.getPhoneNumbers()).hasSize(2);
|
||||
assertThat(fromJson.getPhoneNumbers().toString()).isEqualTo("BeanList size[2] list[64-021-1234, 64-021-4321]");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package org.tests.model.elementcollection;
|
||||
|
||||
import io.ebean.BaseTestCase;
|
||||
import io.ebean.DB;
|
||||
import io.ebean.Ebean;
|
||||
import org.ebeantest.LoggedSqlCollector;
|
||||
import org.junit.Test;
|
||||
@@ -167,4 +168,20 @@ public class TestElementCollectionEmbeddedMap extends BaseTestCase {
|
||||
assertThat(phoneString).contains("64-021-4321");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void json() {
|
||||
|
||||
EcbmPerson person = new EcbmPerson("Fiona64021");
|
||||
person.getPhoneNumbers().put("home", new EcPhone("64", "021","1234"));
|
||||
person.getPhoneNumbers().put("work", new EcPhone("64","021","4321"));
|
||||
|
||||
final String asJson = DB.json().toJson(person);
|
||||
|
||||
assertThat(asJson).isEqualTo("{\"name\":\"Fiona64021\",\"phoneNumbers\":{\"home\":{\"countryCode\":\"64\",\"area\":\"021\",\"number\":\"1234\"},\"work\":{\"countryCode\":\"64\",\"area\":\"021\",\"number\":\"4321\"}}}");
|
||||
|
||||
final EcbmPerson fromJson = DB.json().toBean(EcbmPerson.class, asJson);
|
||||
assertThat(fromJson.getName()).isEqualTo("Fiona64021");
|
||||
assertThat(fromJson.getPhoneNumbers()).hasSize(2);
|
||||
assertThat(fromJson.getPhoneNumbers().toString()).isEqualTo("BeanMap size[2] map{home=64-021-1234, work=64-021-4321}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package org.tests.model.elementcollection;
|
||||
|
||||
import io.ebean.BaseTestCase;
|
||||
import io.ebean.DB;
|
||||
import io.ebean.Ebean;
|
||||
import org.junit.Test;
|
||||
|
||||
@@ -30,4 +31,21 @@ public class TestElementCollectionEnumSet extends BaseTestCase {
|
||||
|
||||
Ebean.save(one);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void json() {
|
||||
|
||||
EcEnumPerson person = new EcEnumPerson("Enum Person");
|
||||
person.getTags().add(EcEnumPerson.Tags.BLUE);
|
||||
person.getTags().add(EcEnumPerson.Tags.RED);
|
||||
|
||||
final String asJson = DB.json().toJson(person);
|
||||
|
||||
assertThat(asJson).isEqualTo("{\"name\":\"Enum Person\",\"tags\":[\"BLUE\",\"RED\"]}");
|
||||
|
||||
final EcEnumPerson fromJson = DB.json().toBean(EcEnumPerson.class, asJson);
|
||||
assertThat(fromJson.getName()).isEqualTo("Enum Person");
|
||||
assertThat(fromJson.getTags()).hasSize(2);
|
||||
assertThat(fromJson.getTags().toString()).isEqualTo("BeanSet size[2] set[BLUE, RED]");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,18 @@
|
||||
package org.tests.query.other;
|
||||
|
||||
import io.ebean.BaseTestCase;
|
||||
import io.ebean.DB;
|
||||
import io.ebean.Ebean;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import org.tests.model.map.MpRole;
|
||||
import org.tests.model.map.MpUser;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
public class TestOneToManyAsMap extends BaseTestCase {
|
||||
|
||||
@Test
|
||||
@@ -39,4 +43,35 @@ public class TestOneToManyAsMap extends BaseTestCase {
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void json() {
|
||||
|
||||
Map<String,MpRole> roles = new LinkedHashMap<>();
|
||||
roles.put("r1", newRole(1L, "r1"));
|
||||
roles.put("r2", newRole(2L, "r2"));
|
||||
|
||||
MpUser u = new MpUser();
|
||||
u.setName("myName");
|
||||
u.setRoles(roles);
|
||||
|
||||
final String asJson = DB.json().toJson(u);
|
||||
|
||||
assertThat(asJson).isEqualTo("{\"name\":\"myName\",\"roles\":[{\"id\":1,\"code\":\"r1\"},{\"id\":2,\"code\":\"r2\"}]}");
|
||||
|
||||
final MpUser bean = DB.json().toBean(MpUser.class, asJson);
|
||||
assertThat(bean.getName()).isEqualTo("myName");
|
||||
final Map<String, MpRole> beanRoles = bean.getRoles();
|
||||
assertThat(beanRoles.get("r1").getId()).isEqualTo(1L);
|
||||
assertThat(beanRoles.get("r1").getCode()).isEqualTo("r1");
|
||||
assertThat(beanRoles.get("r2").getId()).isEqualTo(2L);
|
||||
assertThat(beanRoles.get("r2").getCode()).isEqualTo("r2");
|
||||
}
|
||||
|
||||
private MpRole newRole(Long id, String code) {
|
||||
MpRole role = new MpRole();
|
||||
role.setId(id);
|
||||
role.setCode(code);
|
||||
return role;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -132,7 +132,7 @@ datasource.hsqldb.databaseUrl=jdbc:hsqldb:mem:tests
|
||||
datasource.hsqldb.databaseDriver=org.hsqldb.jdbcDriver
|
||||
|
||||
# Set caseSensitiveCollation to false when using
|
||||
# MySql with case insenstive collation
|
||||
# MySql with case insensitive collation
|
||||
#ebean.mysql.caseSensitiveCollation=false
|
||||
datasource.mysql.username=unit
|
||||
datasource.mysql.password=unit
|
||||
|
||||
Reference in New Issue
Block a user