Skip to content

Commit

Permalink
fixed bug for polymorphic classes with primitives inside
Browse files Browse the repository at this point in the history
  • Loading branch information
asereda-gs committed May 19, 2017
1 parent e22caaf commit 96ab151
Show file tree
Hide file tree
Showing 7 changed files with 220 additions and 16 deletions.
19 changes: 11 additions & 8 deletions gson/src/org/immutables/gson/stream/JsonParserReader.java
Expand Up @@ -27,16 +27,11 @@
import java.io.IOException;
import java.io.Reader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.Callable;

import static com.fasterxml.jackson.core.JsonToken.END_ARRAY;
import static com.fasterxml.jackson.core.JsonToken.END_OBJECT;
import static com.fasterxml.jackson.core.JsonToken.FIELD_NAME;
import static com.fasterxml.jackson.core.JsonToken.START_ARRAY;
import static com.fasterxml.jackson.core.JsonToken.START_OBJECT;
import static com.fasterxml.jackson.core.JsonToken.VALUE_NULL;
import static com.fasterxml.jackson.core.JsonToken.VALUE_STRING;
import static com.fasterxml.jackson.core.JsonToken.*;

/**
* {@link JsonReader} impementation backed by Jackson's {@link JsonParser}.
Expand Down Expand Up @@ -122,6 +117,14 @@ public JsonToken peek() throws IOException {
return toGsonToken(peek);
}

private void expectOneOf(com.fasterxml.jackson.core.JsonToken ... expected) {
for (com.fasterxml.jackson.core.JsonToken token: expected) {
if (peek == token) return;
}

throw new IllegalStateException("Expected one of " + Arrays.asList(expected) + " but was " + peek);
}

private void expect(com.fasterxml.jackson.core.JsonToken expected) {
if (peek != expected) {
throw new IllegalStateException("Expected " + expected + " but was " + peek);
Expand All @@ -141,7 +144,7 @@ public String nextName() throws IOException {
public String nextString() throws IOException {
requirePeek();
if (!isLenient()) {
expect(VALUE_STRING);
expectOneOf(VALUE_STRING, VALUE_NUMBER_FLOAT, VALUE_NUMBER_INT);
}
String value = parser.getText();
clearPeek();
Expand Down
Expand Up @@ -44,11 +44,7 @@ public void readWrite_single() throws Exception {
List<Item> items = repository.find(repository.criteria().id("1")).fetchAll().getUnchecked();

check(items).hasSize(1);

Item item2 = items.get(0);

check(item2.id()).is("1");

check(items.get(0).id()).is("1");
check(repository.findById("1").fetchAll().getUnchecked()).hasSize(1);
}

Expand Down
15 changes: 12 additions & 3 deletions mongo/test/org/immutables/mongo/fixture/MongoContext.java
Expand Up @@ -6,6 +6,9 @@
import com.google.gson.GsonBuilder;
import com.google.gson.TypeAdapterFactory;
import com.mongodb.DB;
import org.immutables.mongo.fixture.holder.Holder;
import org.immutables.mongo.fixture.holder.HolderJsonSerializer;
import org.immutables.mongo.fixture.holder.ImmutableHolder;
import org.immutables.mongo.repository.RepositorySetup;
import org.junit.rules.ExternalResource;

Expand Down Expand Up @@ -41,10 +44,16 @@ public RepositorySetup setup() {
}

private static com.google.gson.Gson createGson() {
GsonBuilder gsonBuilder = new GsonBuilder();
GsonBuilder gson = new GsonBuilder();
for (TypeAdapterFactory factory : ServiceLoader.load(TypeAdapterFactory.class)) {
gsonBuilder.registerTypeAdapterFactory(factory);
gson.registerTypeAdapterFactory(factory);
}
return gsonBuilder.create();

// register custom serializer for polymorphic Holder
final HolderJsonSerializer custom = new HolderJsonSerializer();
gson.registerTypeAdapter(Holder.class, custom);
gson.registerTypeAdapter(ImmutableHolder.class, custom);

return gson.create();
}
}
31 changes: 31 additions & 0 deletions mongo/test/org/immutables/mongo/fixture/holder/Holder.java
@@ -0,0 +1,31 @@
package org.immutables.mongo.fixture.holder;

import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import org.immutables.gson.Gson;
import org.immutables.mongo.Mongo;
import org.immutables.value.Value;

/**
* Data object which can store heterogeneous types
*/
@Gson.TypeAdapters
@Mongo.Repository("holder")
@Value.Immutable
@JsonSerialize(as = ImmutableHolder.class)
@JsonDeserialize(as = ImmutableHolder.class)
public interface Holder {

String TYPE_PROPERTY = "@class";


@Mongo.Id
String id();

/**
* Class name is encoded as JSON attribute ({@code @class}
*/
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY, property = TYPE_PROPERTY)
Object value();
}
@@ -0,0 +1,69 @@
package org.immutables.mongo.fixture.holder;

import com.google.gson.*;

import java.lang.reflect.Type;

/**
* Custom serializer which allows to (JSON) store different types of objects inside same class : {@link Holder}
*/
public class HolderJsonSerializer implements JsonSerializer<Holder>, JsonDeserializer<Holder> {

private static final String VALUE_PROPERTY = "value";

@Override
public Holder deserialize(JsonElement json, Type type, JsonDeserializationContext context) throws JsonParseException {
JsonObject root = (JsonObject) json;

ImmutableHolder.Builder builder = ImmutableHolder.builder();

if (root.has("id")) {
builder.id(root.get("id").getAsString());
}

JsonElement value = root.get(VALUE_PROPERTY);
if (value == null) {
throw new JsonParseException(String.format("%s not found for %s in JSON", VALUE_PROPERTY, type));
}

if (value.isJsonObject()) {
final String valueTypeName = value.getAsJsonObject().get(Holder.TYPE_PROPERTY).getAsString();
try {
Class<?> valueType = Class.forName(valueTypeName);
builder.value(context.deserialize(value, valueType));
} catch (ClassNotFoundException e) {
throw new JsonParseException(String.format("Couldn't construct value class %s for %s", valueTypeName, type) ,e);
}
} else if (value.isJsonPrimitive()) {
final JsonPrimitive primitive = value.getAsJsonPrimitive();
if (primitive.isString()) {
builder.value(primitive.getAsString());
} else if (primitive.isNumber()) {
builder.value(primitive.getAsInt());
} else if (primitive.isBoolean()) {
builder.value(primitive.getAsBoolean());
}
} else {
throw new JsonParseException(String.format("Couldn't deserialize %s : %s. Not a primitive or object", VALUE_PROPERTY, value));
}

return builder.build();

}

@Override
public JsonElement serialize(Holder src, Type type, JsonSerializationContext context) {
JsonObject root = new JsonObject();
JsonElement value = context.serialize(src.value());

root.addProperty("id", src.id());

if (value.isJsonObject()) {
value.getAsJsonObject().addProperty(Holder.TYPE_PROPERTY, src.value().getClass().getName());
}

root.add(VALUE_PROPERTY, value);
return root;
}

}
65 changes: 65 additions & 0 deletions mongo/test/org/immutables/mongo/fixture/holder/HolderTest.java
@@ -0,0 +1,65 @@
package org.immutables.mongo.fixture.holder;

import org.immutables.mongo.fixture.MongoContext;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;

import java.util.List;

import static org.immutables.check.Checkers.check;

public class HolderTest {

@Rule
public final MongoContext context = new MongoContext();

private HolderRepository repository;

@Before
public void setUp() throws Exception {
repository = new HolderRepository(context.setup());
}

/**
* Tests GSON parsing error when using primitives in polymorphic repository class {@link Holder}
* {@code Expected VALUE_STRING but was VALUE_NUMBER_FLOAT}. GSON lazily loads numbers (without parsing the string
* right away) so nextString() token might be number or float instead of string.
*/
@Test
public void primitives() throws Exception {
Primitives prim = ImmutablePrimitives.builder()
.booleanValue(true)
.byteValue((byte) 4)
.shortValue((short) 16)
.intValue(1024)
.longValue(8096)
.floatValue(1.1f)
.doubleValue(3.3d)
.build();

Holder holder = ImmutableHolder.builder().id("h1").value(prim).build();

check(repository.upsert(holder).getUnchecked()).is(1);

final List<Holder> holders = repository.findAll().fetchAll().getUnchecked();

check(holders).hasSize(1);
check(holders.get(0).id()).is("h1");
check(holders.get(0)).is(holder);
}

@Test
public void string() throws Exception {
Holder holder = ImmutableHolder.builder().id("h1").value("foo").build();
check(repository.upsert(holder).getUnchecked()).is(1);
check(repository.findAll().fetchAll().getUnchecked()).has(holder);
}

@Test
public void justInt() throws Exception {
Holder holder = ImmutableHolder.builder().id("h1").value(123).build();
check(repository.upsert(holder).getUnchecked()).is(1);
check(repository.findAll().fetchAll().getUnchecked()).has(holder);
}
}
31 changes: 31 additions & 0 deletions mongo/test/org/immutables/mongo/fixture/holder/Primitives.java
@@ -0,0 +1,31 @@
package org.immutables.mongo.fixture.holder;


import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import org.immutables.gson.Gson;
import org.immutables.value.Value;

/**
* To test the bug with embedded primitives
*/
@Value.Immutable
@JsonSerialize(as = ImmutablePrimitives.class)
@JsonDeserialize(as = ImmutablePrimitives.class)
@Gson.TypeAdapters
public interface Primitives {

boolean booleanValue();

byte byteValue();

short shortValue();

int intValue();

long longValue();

float floatValue();

double doubleValue();
}

0 comments on commit 96ab151

Please sign in to comment.