Skip to content
This repository was archived by the owner on Jun 26, 2021. It is now read-only.

Commit df92c55

Browse files
committed
[#74] When a map is used JSON serialisation is wrong
Add support for dynamic maps.
1 parent 9809683 commit df92c55

10 files changed

Lines changed: 376 additions & 121 deletions

File tree

src/main/java/org/emfjson/common/EObjects.java

Lines changed: 29 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,16 @@
1212
package org.emfjson.common;
1313

1414
import org.eclipse.emf.ecore.*;
15-
import org.eclipse.emf.ecore.impl.DynamicEObjectImpl;
15+
import org.eclipse.emf.ecore.impl.DynamicEObjectImpl.BasicEMapEntry;
1616
import org.eclipse.emf.ecore.util.EcoreUtil;
1717
import org.eclipse.emf.ecore.util.FeatureMap;
1818
import org.eclipse.emf.ecore.util.FeatureMapUtil;
1919
import org.emfjson.jackson.JacksonOptions;
2020

21-
import java.util.*;
21+
import java.util.Collection;
22+
import java.util.Iterator;
23+
import java.util.LinkedHashSet;
24+
import java.util.Set;
2225

2326
/**
2427
* Utility class to facilitate access or modification of eObjects.
@@ -139,8 +142,8 @@ public static boolean isContainmentProxy(EObject owner, EObject contained) {
139142
/**
140143
* Returns set of structural features being part of a feature map.
141144
*
142-
* @param owner
143-
* @param attribute
145+
* @param owner of feature
146+
* @param attribute feature map
144147
* @return set of features
145148
*/
146149
public static Set<EStructuralFeature> featureMaps(EObject owner, EAttribute attribute) {
@@ -158,15 +161,30 @@ public static Set<EStructuralFeature> featureMaps(EObject owner, EAttribute attr
158161
/**
159162
* Creates a map entry of type string, string.
160163
*
161-
* @param key
162-
* @param value
164+
* @param key of entry
165+
* @param value of entry
163166
* @return entry
164167
*/
165-
public static EObject createEntry(String key, String value) {
166-
EObject eObject = EcoreUtil.create(EcorePackage.Literals.ESTRING_TO_STRING_MAP_ENTRY);
167-
eObject.eSet(EcorePackage.Literals.ESTRING_TO_STRING_MAP_ENTRY__KEY, key);
168-
eObject.eSet(EcorePackage.Literals.ESTRING_TO_STRING_MAP_ENTRY__VALUE, value);
169-
return eObject;
168+
@SuppressWarnings("unchecked")
169+
public static EObject createEntry(String key, Object value, EClass type) {
170+
if (type == EcorePackage.Literals.ESTRING_TO_STRING_MAP_ENTRY) {
171+
172+
final EObject entry = EcoreUtil.create(EcorePackage.Literals.ESTRING_TO_STRING_MAP_ENTRY);
173+
entry.eSet(EcorePackage.Literals.ESTRING_TO_STRING_MAP_ENTRY__KEY, key);
174+
entry.eSet(EcorePackage.Literals.ESTRING_TO_STRING_MAP_ENTRY__VALUE, value);
175+
176+
return entry;
177+
178+
} else {
179+
180+
final BasicEMapEntry entry = new BasicEMapEntry<>();
181+
entry.eSetClass(type);
182+
entry.setKey(key);
183+
entry.setValue(value);
184+
185+
return entry;
186+
187+
}
170188
}
171189

172190
}

src/main/java/org/emfjson/jackson/databind/deser/EObjectDeserializer.java

Lines changed: 88 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -26,14 +26,15 @@
2626
import org.eclipse.emf.ecore.resource.ResourceSet;
2727
import org.eclipse.emf.ecore.util.EcoreUtil;
2828
import org.emfjson.EMFJs;
29-
import org.emfjson.jackson.common.Cache;
3029
import org.emfjson.common.EObjects;
3130
import org.emfjson.common.ReferenceEntries;
3231
import org.emfjson.jackson.JacksonOptions;
32+
import org.emfjson.jackson.common.Cache;
3333
import org.emfjson.jackson.errors.JSONException;
3434
import org.emfjson.jackson.resource.JsonResource;
3535

3636
import java.io.IOException;
37+
import java.util.Collection;
3738
import java.util.Map;
3839

3940
public class EObjectDeserializer extends JsonDeserializer<EObject> implements ContextualDeserializer {
@@ -134,7 +135,11 @@ protected EObject doDeserialize(JsonParser jp, EClass defaultType, Deserializati
134135
return postDeserialize(buffer, current, defaultType, ctxt);
135136
}
136137

137-
protected EObject postDeserialize(TokenBuffer buffer, EObject object, EClass defaultType, DeserializationContext ctxt) throws IOException {
138+
protected EObject postDeserialize(TokenBuffer buffer,
139+
EObject object,
140+
EClass defaultType,
141+
DeserializationContext ctxt) throws IOException {
142+
138143
if (object == null && defaultType == null) {
139144
return null;
140145
}
@@ -172,9 +177,12 @@ private EClass findRoot(DeserializationContext ctxt) {
172177
return options.rootElement;
173178
}
174179

175-
private void readFeature(JsonParser jp, EObject current,
176-
String fieldName, DeserializationContext ctxt,
177-
Resource resource, ReferenceEntries entries) throws IOException {
180+
private void readFeature(JsonParser jp,
181+
EObject current,
182+
String fieldName,
183+
DeserializationContext ctxt,
184+
Resource resource,
185+
ReferenceEntries entries) throws IOException {
178186

179187
final EClass eClass = current.eClass();
180188
final EStructuralFeature feature = cache.getEStructuralFeature(eClass, fieldName);
@@ -211,71 +219,50 @@ private void readFeature(JsonParser jp, EObject current,
211219
}
212220

213221
@SuppressWarnings("unchecked")
214-
private void readContainment(JsonParser jp, EObject owner,
215-
DeserializationContext ctxt, EReference reference,
216-
Resource resource, ReferenceEntries entries) throws IOException {
222+
private void readContainment(JsonParser jp,
223+
EObject owner,
224+
DeserializationContext ctxt,
225+
EReference reference,
226+
Resource resource,
227+
ReferenceEntries entries) throws IOException {
217228

218-
final Class<?> type = reference.getEReferenceType().getInstanceClass();
229+
final EClass eType = (EClass) owner.eClass().getFeatureType(reference).getEClassifier();
230+
final Class<?> type = eType.getInstanceClass();
219231

220-
if (type != null && Map.Entry.class.isAssignableFrom(type)) {
221-
EMap map = (EMap) owner.eGet(reference);
222-
223-
if (jp.getCurrentToken() == JsonToken.START_OBJECT) {
232+
if (jp.getCurrentToken() == JsonToken.START_ARRAY) {
224233

225-
while (jp.nextToken() != JsonToken.END_OBJECT) {
226-
EObject entry = EObjects.createEntry(jp.getCurrentName(), jp.nextTextValue());
227-
map.add(entry);
228-
}
234+
while (jp.nextToken() != JsonToken.END_ARRAY) {
229235

230-
} else if (jp.getCurrentToken() == JsonToken.START_ARRAY) {
231-
232-
while (jp.nextToken() != JsonToken.END_ARRAY) {
233-
EObject key = null;
234-
EObject value = null;
235-
236-
if (jp.getCurrentToken() == JsonToken.START_OBJECT) {
237-
while (jp.nextToken() != JsonToken.END_OBJECT) {
238-
if ("key".equals( jp.getCurrentName() )) {
239-
jp.nextToken();
240-
key = deserialize(jp, ctxt, reference);
241-
} else if ("value".equals( jp.getCurrentName() )) {
242-
jp.nextToken();
243-
value = deserialize(jp, ctxt, reference);
244-
}
245-
}
236+
try {
237+
EObject value = deserialize(jp, ctxt, reference);
238+
if (value != null) {
239+
EObjects.setOrAdd(owner, reference, value);
240+
entries.store(resource, value);
246241
}
247-
248-
if (key != null && value != null) {
249-
map.put(key, value);
242+
} catch (Exception e) {
243+
if (resource != null) {
244+
resource.getErrors().add(new JSONException(e, jp.getCurrentLocation()));
250245
}
251246
}
252247
}
253248

254249
} else {
255250

256-
if (jp.getCurrentToken() == JsonToken.START_ARRAY) {
257-
while (jp.nextToken() != JsonToken.END_ARRAY) {
258-
259-
try {
260-
EObject value = deserialize(jp, ctxt, reference);
261-
if (value != null) {
262-
EObjects.setOrAdd(owner, reference, value);
263-
entries.store(resource, value);
264-
}
265-
} catch (Exception e) {
266-
if (resource != null) {
267-
resource.getErrors().add(new JSONException(e, jp.getCurrentLocation()));
268-
}
269-
}
270-
}
271-
} else {
251+
// if the type is a Map.Entry but the current values are not serialized
252+
// in an array, then it must be a map with string keys that was serialized
253+
// as a JSON object.
254+
if ((type != null && Map.Entry.class.isAssignableFrom(type)) || "java.util.Map.Entry".equals(eType.getInstanceClassName())) {
272255

256+
readMap(jp, owner, ctxt, reference);
257+
258+
} else {
259+
// otherwise it's a normal object.
273260
try {
274261
EObject value = deserialize(jp, ctxt, reference);
275262
if (value != null) {
276263
entries.store(resource, value);
264+
EObjects.setOrAdd(owner, reference, value);
277265
}
278-
EObjects.setOrAdd(owner, reference, value);
279266
} catch (Exception e) {
280267
if (resource != null) {
281268
resource.getErrors().add(new JSONException(e, jp.getCurrentLocation()));
@@ -285,8 +272,45 @@ private void readContainment(JsonParser jp, EObject owner,
285272
}
286273
}
287274

288-
private void readReference(JsonParser jp, EObject owner,
289-
EReference reference, ReferenceEntries entries,
275+
/*
276+
Read the key-values pair of a JSON object and put it in a Map.
277+
*/
278+
@SuppressWarnings("unchecked")
279+
private void readMap(JsonParser jp,
280+
EObject owner,
281+
DeserializationContext ctxt,
282+
EReference reference) throws IOException {
283+
284+
final Collection map = (Collection) owner.eGet(reference);
285+
286+
if (jp.getCurrentToken() == JsonToken.START_OBJECT) {
287+
288+
while (jp.nextToken() != JsonToken.END_OBJECT) {
289+
final String key = jp.getCurrentName();
290+
jp.nextToken();
291+
292+
final Object value;
293+
if (jp.getCurrentToken() == JsonToken.START_OBJECT) {
294+
value = deserialize(jp, ctxt, reference);
295+
} else {
296+
value = ctxt.readValue(jp, Object.class);
297+
}
298+
299+
// Dynamic objects do not use the EMap interface
300+
// but store entries in a DynamicEList instead.
301+
if (map instanceof EMap) {
302+
((EMap) map).put(key, value);
303+
} else {
304+
map.add(EObjects.createEntry(key, value, reference.getEReferenceType()));
305+
}
306+
}
307+
}
308+
}
309+
310+
private void readReference(JsonParser jp,
311+
EObject owner,
312+
EReference reference,
313+
ReferenceEntries entries,
290314
DeserializationContext context) throws IOException {
291315

292316
if (jp.getCurrentToken() == JsonToken.START_ARRAY) {
@@ -298,9 +322,12 @@ private void readReference(JsonParser jp, EObject owner,
298322
}
299323
}
300324

301-
private void readAttribute(JsonParser jp, EObject owner,
302-
EAttribute attribute, Resource resource,
325+
private void readAttribute(JsonParser jp,
326+
EObject owner,
327+
EAttribute attribute,
328+
Resource resource,
303329
DeserializationContext ctxt) throws IOException {
330+
304331
final EDataType dataType = (EDataType) owner.eClass().getFeatureType(attribute).getEClassifier();
305332
if (dataType == null) {
306333
resource.getErrors().add(new JSONException("Missing feature type", jp.getCurrentLocation()));
@@ -317,8 +344,10 @@ private void readAttribute(JsonParser jp, EObject owner,
317344
}
318345
}
319346

320-
private void readSingleAttribute(JsonParser jp, EObject owner,
321-
EAttribute attribute, Resource resource,
347+
private void readSingleAttribute(JsonParser jp,
348+
EObject owner,
349+
EAttribute attribute,
350+
Resource resource,
322351
EDataType dataType,
323352
DeserializationContext ctxt) throws IOException {
324353

src/main/java/org/emfjson/jackson/databind/ser/EStringToStringMapEntrySerializer.java renamed to src/main/java/org/emfjson/jackson/databind/ser/EMapEntrySerializer.java

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,24 +11,29 @@
1111
*/
1212
package org.emfjson.jackson.databind.ser;
1313

14-
import org.eclipse.emf.ecore.impl.EStringToStringMapEntryImpl;
15-
1614
import com.fasterxml.jackson.core.JsonGenerator;
1715
import com.fasterxml.jackson.databind.JsonSerializer;
1816
import com.fasterxml.jackson.databind.SerializerProvider;
1917

2018
import java.io.IOException;
19+
import java.util.Map;
2120

22-
public class EStringToStringMapEntrySerializer extends JsonSerializer<EStringToStringMapEntryImpl> {
21+
public class EMapEntrySerializer extends JsonSerializer<Map.Entry> {
2322

2423
@Override
25-
public void serialize(EStringToStringMapEntryImpl value, JsonGenerator jg, SerializerProvider provider) throws IOException {
26-
jg.writeStringField(value.getKey(), value.getValue());
24+
public void serialize(Map.Entry entry, JsonGenerator jg, SerializerProvider serializers) throws IOException {
25+
if (entry.getKey() instanceof String) {
26+
jg.writeObjectField((String) entry.getKey(), entry.getValue());
27+
} else {
28+
jg.writeStartObject();
29+
jg.writeObjectField("key", entry.getKey());
30+
jg.writeObjectField("value", entry.getValue());
31+
jg.writeEndObject();
32+
}
2733
}
2834

2935
@Override
30-
public Class<EStringToStringMapEntryImpl> handledType() {
31-
return EStringToStringMapEntryImpl.class;
36+
public Class<Map.Entry> handledType() {
37+
return Map.Entry.class;
3238
}
33-
3439
}

src/main/java/org/emfjson/jackson/databind/ser/EMapSerializer.java

Lines changed: 19 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -14,36 +14,34 @@
1414
import com.fasterxml.jackson.core.JsonGenerator;
1515
import com.fasterxml.jackson.databind.JsonSerializer;
1616
import com.fasterxml.jackson.databind.SerializerProvider;
17-
import org.eclipse.emf.common.util.BasicEMap;
1817
import org.eclipse.emf.common.util.EMap;
1918

2019
import java.io.IOException;
20+
import java.util.Map;
2121

2222
public class EMapSerializer extends JsonSerializer<EMap> {
2323

2424
@Override
25-
public void serialize(EMap value, JsonGenerator jg, SerializerProvider provider) throws IOException {
26-
if (value.isEmpty()) {
27-
jg.writeStartArray();
28-
jg.writeEndArray();
25+
public void serialize(EMap value, JsonGenerator jg, SerializerProvider serializers) throws IOException {
26+
if (value.keySet().isEmpty()) {
27+
jg.writeNull();
28+
return;
29+
}
30+
31+
final Map.Entry firstEntry = (Map.Entry) value.get(0);
32+
33+
if (firstEntry.getKey() instanceof String) {
34+
jg.writeStartObject();
35+
for (Object entry: value) {
36+
jg.writeObject(entry);
37+
}
38+
jg.writeEndObject();
2939
} else {
30-
BasicEMap.Entry<?, ?> o = (BasicEMap.Entry<?, ?>) value.get(0);
31-
if (o.getKey() instanceof String) {
32-
jg.writeStartObject();
33-
for (Object entry : value.entrySet()) {
34-
jg.writeObject(entry);
35-
}
36-
jg.writeEndObject();
37-
} else {
38-
jg.writeStartArray();
39-
for (Object key: value.keySet()) {
40-
jg.writeStartObject();
41-
jg.writeObjectField("key", key);
42-
jg.writeObjectField("value", value.get(key));
43-
jg.writeEndObject();
44-
}
45-
jg.writeEndArray();
40+
jg.writeStartArray();
41+
for (Object entry: value) {
42+
jg.writeObject(entry);
4643
}
44+
jg.writeEndArray();
4745
}
4846
}
4947

@@ -53,4 +51,3 @@ public Class<EMap> handledType() {
5351
}
5452

5553
}
56-

src/main/java/org/emfjson/jackson/module/EMFModule.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ protected void configure() {
5353
addSerializer(Enumerator.class, new EnumeratorSerializer());
5454
addSerializer(EEnumLiteral.class, new EnumeratorSerializer());
5555
addSerializer(new EMapSerializer());
56-
addSerializer(new EStringToStringMapEntrySerializer());
56+
addSerializer(new EMapEntrySerializer());
5757
addDeserializer(EObject.class, new EObjectDeserializer(resourceSet, options));
5858
addDeserializer(Resource.class, new ResourceDeserializer(resourceSet, options));
5959
}

0 commit comments

Comments
 (0)