Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
Strongbeard opened this issue Jun 18, 2024 · 1 comment · May be fixed by #69

Comments

@Strongbeard
Copy link

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?
@Strongbeard Strongbeard linked a pull request Jun 19, 2024 that will close this issue
@Strongbeard
Copy link
Author

For anyone waiting for this to be resolved, you can work-around the issue by adding the following mixin to your ObjectMapper:

import java.util.List;
import com.fasterxml.jackson.annotation.JsonFormat;

public abstract class GeometryMixin<T> {
    @JsonFormat(without = JsonFormat.Feature.WRITE_SINGLE_ELEM_ARRAYS_UNWRAPPED)
    protected List<T> coordinates;
}
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import org.geojson.Geometry;
// import <path-to-mixin>.GeometryMixin;

ObjectMapper mapper = new ObjectMapper()
    .enable(SerializationFeature.WRITE_SINGLE_ELEM_ARRAYS_UNWRAPPED)
    .addMixIn(Geometry.class, GeometryMixin.class);

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

1 participant