Skip to content

Commit 371ca60

Browse files
committed
improved error messages for wrongly provided dimensions
1 parent 3d48c3d commit 371ca60

File tree

3 files changed

+117
-45
lines changed

3 files changed

+117
-45
lines changed

src/main/java/org/tensorics/gson/adapters/TensorbackedGsonAdapter.java

Lines changed: 28 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,13 @@
22

33
import com.google.common.annotations.VisibleForTesting;
44
import com.google.gson.Gson;
5+
import com.google.gson.JsonSyntaxException;
56
import com.google.gson.TypeAdapter;
7+
import com.google.gson.TypeAdapterFactory;
68
import com.google.gson.internal.JsonReaderInternalAccess;
79
import com.google.gson.reflect.TypeToken;
810
import com.google.gson.stream.JsonReader;
11+
import com.google.gson.stream.JsonToken;
912
import com.google.gson.stream.JsonWriter;
1013
import org.tensorics.core.lang.Tensorics;
1114
import org.tensorics.core.tensor.Position;
@@ -24,6 +27,8 @@
2427

2528
public class TensorbackedGsonAdapter<V, TB extends Tensorbacked<V>> extends TypeAdapter<TB> {
2629

30+
public static final TypeAdapterFactory FACTORY = new TensorbackedGsonAdapterFactory();
31+
2732
private final Gson context;
2833
private final Class<TB> tensorbackedClass;
2934

@@ -74,16 +79,30 @@ Object recursiveRead(JsonReader in, List<Class<?>> dimensions) throws IOExceptio
7479
private <T> Map<T, Object> readMap(JsonReader in, Class<T> keyDim, List<Class<?>> remainingDimensions) throws IOException {
7580
TypeAdapter<T> dimAdapter = adapterFor(keyDim);
7681
Map<T, Object> map = new HashMap<>();
77-
78-
in.beginObject();
79-
while (in.hasNext()) {
80-
JsonReaderInternalAccess.INSTANCE.promoteNameToValue(in);
81-
T key = dimAdapter.read(in);
82-
Object value = recursiveRead(in, remainingDimensions);
83-
map.put(key, value);
82+
JsonToken peek = in.peek();
83+
if (peek == JsonToken.BEGIN_ARRAY) {
84+
in.beginArray();
85+
while (in.hasNext()) {
86+
in.beginArray(); // entry array
87+
T key = dimAdapter.read(in);
88+
Object value = recursiveRead(in, remainingDimensions);
89+
Object replaced = map.put(key, value);
90+
if (replaced != null) {
91+
throw new JsonSyntaxException("duplicate key: " + key);
92+
}
93+
in.endArray();
94+
}
95+
in.endArray();
96+
} else {
97+
in.beginObject();
98+
while (in.hasNext()) {
99+
JsonReaderInternalAccess.INSTANCE.promoteNameToValue(in);
100+
T key = dimAdapter.read(in);
101+
Object value = recursiveRead(in, remainingDimensions);
102+
map.put(key, value);
103+
}
104+
in.endObject();
84105
}
85-
in.endObject();
86-
87106
return map;
88107
}
89108

@@ -92,29 +111,4 @@ private <T> TypeAdapter<T> adapterFor(Class<T> valueType) {
92111
return context.getAdapter(TypeToken.get(valueType));
93112
}
94113

95-
96-
/*
97-
JsonToken peek = in.peek();
98-
if (peek == JsonToken.NULL) {
99-
in.nextNull();
100-
return null;
101-
}
102-
103-
Map<K, V> map = constructor.construct();
104-
105-
if (peek == JsonToken.BEGIN_ARRAY) {
106-
in.beginArray();
107-
while (in.hasNext()) {
108-
in.beginArray(); // entry array
109-
K key = keyTypeAdapter.read(in);
110-
V value = valueTypeAdapter.read(in);
111-
V replaced = map.put(key, value);
112-
if (replaced != null) {
113-
throw new JsonSyntaxException("duplicate key: " + key);
114-
}
115-
in.endArray();
116-
}
117-
in.endArray();
118-
}
119-
*/
120114
}

src/main/java/org/tensorics/gson/adapters/TensorbackedGsonAdapterFactory.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import com.google.gson.reflect.TypeToken;
77
import org.tensorics.core.tensorbacked.Tensorbacked;
88

9-
public class TensorbackedGsonAdapterFactory implements TypeAdapterFactory {
9+
class TensorbackedGsonAdapterFactory implements TypeAdapterFactory {
1010

1111

1212
@Override

src/test/java/org/tensorics/gson/adapters/TensorbackedGsonAdapterTest.java

Lines changed: 88 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,21 @@
22

33
import com.google.gson.Gson;
44
import com.google.gson.GsonBuilder;
5+
import com.google.gson.JsonSyntaxException;
56
import org.junit.Test;
67
import org.tensorics.core.lang.Tensorics;
78
import org.tensorics.core.tensor.Position;
89
import org.tensorics.core.tensor.Tensor;
910
import org.tensorics.core.tensorbacked.AbstractTensorbacked;
1011
import org.tensorics.core.tensorbacked.annotation.Dimensions;
12+
import org.tensorics.core.tensorbacked.dimtyped.Tensorbacked1d;
1113
import org.tensorics.core.tensorbacked.dimtyped.Tensorbacked2d;
1214
import org.tensorics.core.tensorbacked.dimtyped.TensorbackedScalar;
1315

16+
import java.util.Objects;
17+
1418
import static org.assertj.core.api.Assertions.assertThat;
19+
import static org.assertj.core.api.Assertions.assertThatThrownBy;
1520
import static org.tensorics.core.lang.Tensorics.at;
1621
import static org.tensorics.core.lang.Tensorics.sizeOf;
1722

@@ -26,21 +31,33 @@ public class TensorbackedGsonAdapterTest {
2631

2732
private static final String JSON_STRING = "{\"A\":{\"1\":0.11,\"2\":0.12},\"B\":{\"1\":0.21,\"2\":0.22}}";
2833

29-
private final Gson gson = new GsonBuilder()//
30-
.registerTypeAdapterFactory(new TensorbackedGsonAdapterFactory())//
34+
private final Gson simpleGson = new GsonBuilder()//
35+
.registerTypeAdapterFactory(TensorbackedGsonAdapter.FACTORY)//
36+
.create();
37+
38+
private static final AComplexCoordTensorbacked COMPLEX_COORD_TB = Tensorics.builderFor(AComplexCoordTensorbacked.class)//
39+
.put(at(new Pair("a1", "b1")), 0.11)//
40+
.put(at(new Pair("a2", "b2")), 0.22)//
41+
.build();
42+
private static final String COMPLEX_COORD_JSON_STRING = "[[{\"a\":\"a1\",\"b\":\"b1\"},0.11],[{\"a\":\"a2\",\"b\":\"b2\"},0.22]]";
43+
44+
private final Gson complexMapKeyGson = new GsonBuilder()//
45+
.registerTypeAdapterFactory(TensorbackedGsonAdapter.FACTORY)//
46+
.enableComplexMapKeySerialization() //
3147
.create();
3248

49+
3350
@Test
3451
public void simpleTensorSerializationIsOk() {
35-
String string = gson.toJson(TENSORBACKED);
52+
String string = simpleGson.toJson(TENSORBACKED);
3653
assertThat(string).isNotNull();
3754
assertThat(string).isNotEmpty();
3855
assertThat(string).isEqualTo(JSON_STRING);
3956
}
4057

4158
@Test
4259
public void simpleTensorDeserializationIsOk() {
43-
AnInheritedTensorbacked val = gson.fromJson(JSON_STRING, AnInheritedTensorbacked.class);
60+
AnInheritedTensorbacked val = simpleGson.fromJson(JSON_STRING, AnInheritedTensorbacked.class);
4461
assertThat(val).isNotNull();
4562
assertThat(val).isEqualTo(TENSORBACKED);
4663
}
@@ -49,15 +66,15 @@ public void simpleTensorDeserializationIsOk() {
4966
public void simpleScalarSerializationIsOk() {
5067
AScalarBacked val = Tensorics.builderForScalar(AScalarBacked.class).put(0.33).build();
5168

52-
String string = gson.toJson(val);
69+
String string = simpleGson.toJson(val);
5370
assertThat(string).isNotNull();
5471
assertThat(string).isNotEmpty();
5572
assertThat(string).isEqualTo("0.33");
5673
}
5774

5875
@Test
5976
public void simpleScalarDeserializationIsOk() {
60-
AScalarBacked val = gson.fromJson("0.33", AScalarBacked.class);
77+
AScalarBacked val = simpleGson.fromJson("0.33", AScalarBacked.class);
6178
assertThat(val).isNotNull();
6279
assertThat(sizeOf(val)).isEqualTo(1);
6380
assertThat(val.get()).isEqualTo(0.33);
@@ -71,7 +88,7 @@ public void simpleScalarDeserializationIsOk() {
7188
@Test
7289
public void deserializationIntoDifferentTbWorks() {
7390
/* This shows cross-deserialization: Into another tensoribacked with the same dimensions.*/
74-
AnInterfaceTensorbacked val = gson.fromJson(JSON_STRING, AnInterfaceTensorbacked.class);
91+
AnInterfaceTensorbacked val = simpleGson.fromJson(JSON_STRING, AnInterfaceTensorbacked.class);
7592
assertThat(val).isNotNull();
7693

7794
/* The tensorbacked objects are not equal in this case ....*/
@@ -82,7 +99,7 @@ public void deserializationIntoDifferentTbWorks() {
8299
}
83100

84101
/**
85-
* The context is not serialized or deserialzed currently. This is demonstrated here.
102+
* The context is not serialized or deserialized currently. This is demonstrated here.
86103
* Can be discussed, if this is good behaviour.... tricky to change anyhow, as the context
87104
* dimensions are not well defined by a tensorbacked.
88105
*/
@@ -93,8 +110,8 @@ public void contextIsNotSerialized() {
93110
.context(Position.of(AB.A))//
94111
.build();
95112

96-
String string = gson.toJson(tbWithContext);
97-
AnInheritedTensorbacked deserialized = gson.fromJson(string, AnInheritedTensorbacked.class);
113+
String string = simpleGson.toJson(tbWithContext);
114+
AnInheritedTensorbacked deserialized = simpleGson.fromJson(string, AnInheritedTensorbacked.class);
98115

99116
/* As the context is currently neither serialized nor deserialized,
100117
the equality to the original object does currently not hold.*/
@@ -105,6 +122,32 @@ public void contextIsNotSerialized() {
105122
assertThat(deserialized).isEqualTo(TENSORBACKED);
106123
}
107124

125+
@Test
126+
public void complexCoordinateNotSupportedPerDefault() {
127+
String string = simpleGson.toJson(COMPLEX_COORD_TB);
128+
129+
/* This cannot be deserialized anymore, as the simple result of "toString" was put as key...*/
130+
assertThatThrownBy(() -> simpleGson.fromJson(string, AComplexCoordTensorbacked.class)) //
131+
.isInstanceOf(JsonSyntaxException.class) //
132+
.hasMessageContaining("Expected BEGIN_OBJECT but was STRING");
133+
}
134+
135+
@Test
136+
public void complexCoordinateSerializationWithComplexMapKeySupport() {
137+
String string = complexMapKeyGson.toJson(COMPLEX_COORD_TB);
138+
System.out.println(string);
139+
assertThat(string).isEqualTo(COMPLEX_COORD_JSON_STRING);
140+
141+
/* deserialization works, no matter if the flag is set or not */
142+
AComplexCoordTensorbacked deserialized = simpleGson.fromJson(string, AComplexCoordTensorbacked.class);
143+
}
144+
145+
@Test
146+
public void complexCoordinateDeserializationIsOk() {
147+
/* deserialization works with any gson (as it is an if in the adapter) */
148+
AComplexCoordTensorbacked deserialized = simpleGson.fromJson(COMPLEX_COORD_JSON_STRING, AComplexCoordTensorbacked.class);
149+
assertThat(deserialized).isEqualTo(COMPLEX_COORD_TB);
150+
}
108151

109152
public interface AScalarBacked extends TensorbackedScalar<Double> {
110153

@@ -124,8 +167,43 @@ public static interface AnInterfaceTensorbacked extends Tensorbacked2d<String, I
124167

125168
}
126169

170+
public static interface AComplexCoordTensorbacked extends Tensorbacked1d<Pair, Double> {
171+
172+
}
173+
127174
public static enum AB {
128175
A, B
129176
}
130177

178+
public static class Pair {
179+
public final String a;
180+
public final String b;
181+
182+
public Pair(String a, String b) {
183+
this.a = a;
184+
this.b = b;
185+
}
186+
187+
@Override
188+
public boolean equals(Object o) {
189+
if (this == o) return true;
190+
if (o == null || getClass() != o.getClass()) return false;
191+
Pair pair = (Pair) o;
192+
return Objects.equals(a, pair.a) && Objects.equals(b, pair.b);
193+
}
194+
195+
@Override
196+
public int hashCode() {
197+
return Objects.hash(a, b);
198+
}
199+
200+
@Override
201+
public String toString() {
202+
return "Pair{" +
203+
"a='" + a + '\'' +
204+
", b='" + b + '\'' +
205+
'}';
206+
}
207+
}
208+
131209
}

0 commit comments

Comments
 (0)