Skip to content

Turning on the SerializationFeature.WRITE_SINGLE_ELEM_ARRAYS_UNWRAPPED and DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY of ObjectMapper breaks LngLatAlt deserialization #68

Description

@Strongbeard

Software Versions:

  • Java: 17.0.11-amzn managed by sdkman
  • Jackson: 2.15.4
  • geojson-jackson: 1.14

Executive Summary

The LngLatAltSerializer and LngLatAltDeserializer cannot properly handle the SerializationFeature.WRITE_SINGLE_ELEM_ARRAYS_UNWRAPPED and DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY features being turned on in an ObjectMapper.

Details

Running the following code throws the below redacted exception. Substituting the Polygon with a similar MultiPolygon or MultiLineString should produce a similar exception.

ObjectMapper mapper = new ObjectMapper()
    .enable(SerializationFeature.INDENT_OUTPUT)
    .enable(SerializationFeature.WRITE_SINGLE_ELEM_ARRAYS_UNWRAPPED)
    .enable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY);
Polygon polygon = new Polygon(List.of(
    new LngLatAlt(0.0, 0.0),
    new LngLatAlt(1.0, 0.0),
    new LngLatAlt(0.0, 1.0),
    new LngLatAlt(0.0, 0.0)
));
String str = mapper.writeValueAsString(polygon);
mapper.readValue(str, Polygon.class);
com.fasterxml.jackson.databind.JsonMappingException: Cannot deserialize instance of `org.geojson.LngLatAlt` out of VALUE_NUMBER_FLOAT token
 at [Source: (String)"{
  "type" : "Polygon",
  "coordinates" : [ [ 0.0, 0.0 ], [ 1.0, 0.0 ], [ 0.0, 1.0 ], [ 0.0, 0.0 ] ]
}"; line: 3, column: 23] (through reference chain: org.geojson.Polygon["coordinates"]-]java.util.ArrayList[0]-]java.util.ArrayList[0])
  at com.fasterxml.jackson.databind.JsonMappingException.from(JsonMappingException.java:269)
  at com.fasterxml.jackson.databind.DeserializationContext.mappingException(DeserializationContext.java:2201)
  at com.fasterxml.jackson.databind.DeserializationContext.mappingException(DeserializationContext.java:2193)
  at org.geojson.jackson.LngLatAltDeserializer.deserialize(LngLatAltDeserializer.java:20)
  at org.geojson.jackson.LngLatAltDeserializer.deserialize(LngLatAltDeserializer.java:13)
  at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer._deserializeFromArray(CollectionDeserializer.java:359)
  at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.deserialize(CollectionDeserializer.java:244)
  at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.deserialize(CollectionDeserializer.java:28)
  at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer._deserializeFromArray(CollectionDeserializer.java:359)
  at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.deserialize(CollectionDeserializer.java:244)
  at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.deserialize(CollectionDeserializer.java:28)
  at com.fasterxml.jackson.databind.deser.impl.MethodProperty.deserializeAndSet(MethodProperty.java:129)
  at com.fasterxml.jackson.databind.deser.BeanDeserializer.vanillaDeserialize(BeanDeserializer.java:314)
  at com.fasterxml.jackson.databind.deser.BeanDeserializer._deserializeOther(BeanDeserializer.java:215)
  at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:187)
  at com.fasterxml.jackson.databind.jsontype.impl.AsPropertyTypeDeserializer._deserializeTypedForId(AsPropertyTypeDeserializer.java:170)
  at com.fasterxml.jackson.databind.jsontype.impl.AsPropertyTypeDeserializer.deserializeTypedFromObject(AsPropertyTypeDeserializer.java:136)
  at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.deserializeWithType(BeanDeserializerBase.java:1306)
  at com.fasterxml.jackson.databind.deser.impl.TypeWrappedDeserializer.deserialize(TypeWrappedDeserializer.java:74)
  at com.fasterxml.jackson.databind.deser.DefaultDeserializationContext.readRootValue(DefaultDeserializationContext.java:323)
  at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4825)
  at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3772)
  at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3740)
  at ██████████(██████████.java:██████████)
  at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
  at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)

I believe the issue stems from the following two json Strings that are produced and then interpreted by mapper.readValue(str, Polygon.class) when SerializationFeature.WRITE_SINGLE_ELEM_ARRAYS_UNWRAPPED is turned on or off, respectively. The former throws the exception while the latter does not, due to expecting the 3rd set of array brackets. When the feature is turned on, the outermost brackets are not written since they are "unwrapped" by the feature. Thus, in the former scenario, jackson "opens" both arrays since it knows from the type that there are 2 outer list/array types and tries to pass the 1st DoubleNode to the LngLatAltDeserializer, which expects the [ token instead.

{
  "type" : "Polygon",
  "coordinates" : [ [ 0.0, 0.0 ], [ 1.0, 0.0 ], [ 0.0, 1.0 ], [ 0.0, 0.0 ] ]
}
{
  "type" : "Polygon",
  "coordinates" : [ [ [ 0.0, 0.0 ], [ 1.0, 0.0 ], [ 0.0, 1.0 ], [ 0.0, 0.0 ] ] ]
}

Potential Solutions

  1. Use annotations to turn off the SerializationFeature.WRITE_SINGLE_ELEM_ARRAYS_UNWRAPPED and DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY features for only the geojson classes so that said features can still be turned on for the surrounding json document without breaking serialization/deserialization of the geojson. This might be the prefered solution since RFC7946 does not yet appear to have any examples of "unwrapped single element arrays".
  2. Perhaps there is a way to indicate to the jackson library that LngLatAlt objects should be treated like an array when serializing/deserializing them so it properly generates the 3rd wrapping ArrayNode even when the above features are turned on? Maybe either making LngLatAlt extend List or having the LngLatAltDeserializer advertise that it produces an array by overriding the hangleType() function to return List.class or logicalType() to return LogicalType.Array?

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions