emfjson / emfjson-jackson

JSON Binding for Eclipse Modeling Framework
https://emfjson.github.io
Other
80 stars 23 forks source link

EMapDeserializer creates ArrayList instead of EList when map value is a list #115

Open krissrex opened 3 years ago

krissrex commented 3 years ago

Description: I am trying to deserialize an EMap with BasicEMap.Entry<String,EList<String>> as its contents. Example of this structure:

{ 
  "myMap": { 
    "someKey": ["a", "b", "c"],
    "key2": ["a", "b"] 
  }
}

It uses this construct from Ecore:

public class StringToStringsMapImpl extends MinimalEObjectImpl.Container implements BasicEMap.Entry<String,EList<String>> {

  // ...

    @Override
    public EList<String> setValue(EList<String> value) {
        EList<String> oldValue = getValue();
        getTypedValue().clear();
        getTypedValue().addAll(value);
        return oldValue;
    }

I have a valid json that I created with this library. I then try to deserialize it back into this EMap.

Expected: The values in the map entries would be EList instances.

Actual: ClassCast exceptions. The org.emfjson.jackson.databind.deser.EMapDeserializer sees a token that is not START_OBJECT (but rather START_ARRAY) and performs a readValue with Object.class. Jackson does its lookups and concludes a com.fasterxml.jackson.databind.deser.std.UntypedObjectDeserializer should be used, and this creates an ArrayList at line 840. The org.eclipse.emf.ecore.util.EcoreEMap runs a setValue on line 270 with the ArrayList value, and this triggers a ClassCastException because this list should have been an EList.

Stacktrace:

Exception in thread "main" java.lang.ClassCastException: class java.util.ArrayList cannot be cast to class org.eclipse.emf.common.util.EList (java.util.ArrayList is in module java.base of loader 'bootstrap'; org.eclipse.emf.common.util.EList is in unnamed module of loader 'app')
    at no.ntnu.stud.treedocumentmodel.impl.NodeTypeToNodeTypesMapImpl.setValue(NodeTypeToNodeTypesMapImpl.java:1)
    at org.eclipse.emf.ecore.util.EcoreEMap.newEntry(EcoreEMap.java:270)
    at org.eclipse.emf.common.util.BasicEMap.put(BasicEMap.java:584)
    at org.emfjson.jackson.databind.deser.EMapDeserializer.deserialize(EMapDeserializer.java:66)
    at org.emfjson.jackson.databind.deser.EMapDeserializer.deserialize(EMapDeserializer.java:30)
    at org.emfjson.jackson.databind.property.EObjectFeatureProperty.deserializeAndSet(EObjectFeatureProperty.java:64)
    at org.emfjson.jackson.databind.deser.EObjectDeserializer.deserialize(EObjectDeserializer.java:78)
    at org.emfjson.jackson.databind.deser.EObjectDeserializer.deserialize(EObjectDeserializer.java:38)
    at com.fasterxml.jackson.databind.ObjectReader._bindAndClose(ObjectReader.java:1608)
    at com.fasterxml.jackson.databind.ObjectReader.readValue(ObjectReader.java:1292)

Assumed fixes:

  1. Perhaps do a check for START_ARRAY in EMapDeserializer#57 and treat this differently?
  2. Register EList as a deserialization class for json array
  3. Check in EMapDeserializer if the value is ArrayList and convert it to EList. I think this is a bad solution; more of a band-aid fix for this particular case

Thanks!