diff --git a/guava/src/main/java/com/fasterxml/jackson/datatype/guava/GuavaDeserializers.java b/guava/src/main/java/com/fasterxml/jackson/datatype/guava/GuavaDeserializers.java index 9a55c250..28989c46 100644 --- a/guava/src/main/java/com/fasterxml/jackson/datatype/guava/GuavaDeserializers.java +++ b/guava/src/main/java/com/fasterxml/jackson/datatype/guava/GuavaDeserializers.java @@ -1,5 +1,8 @@ package com.fasterxml.jackson.datatype.guava; +import com.fasterxml.jackson.datatype.guava.deser.table.HashBasedTableDeserializer; +import com.fasterxml.jackson.datatype.guava.deser.table.ImmutableTableDeserializer; +import com.fasterxml.jackson.datatype.guava.deser.table.TreeBasedTableDeserializer; import java.io.Serializable; import com.google.common.base.Optional; @@ -259,8 +262,15 @@ public JsonDeserializer findMapLikeDeserializer(MapLikeType type, } if (Table.class.isAssignableFrom(raw)) { - // !!! TODO + if (HashBasedTable.class.isAssignableFrom(raw)) { + return new HashBasedTableDeserializer(type); + } + if (TreeBasedTable.class.isAssignableFrom(raw)) { + return new TreeBasedTableDeserializer(type); + } + return new ImmutableTableDeserializer(type); } + // @since 2.16 : support Cache deserialization java.util.Optional> cacheDeserializer = findCacheDeserializer(raw, type, config, beanDesc, keyDeserializer, elementTypeDeserializer, elementDeserializer); @@ -315,7 +325,7 @@ public JsonDeserializer findReferenceDeserializer(ReferenceType refType, public JsonDeserializer findBeanDeserializer(final JavaType type, DeserializationConfig config, BeanDescription beanDesc) { - if (RangeSet.class.isAssignableFrom(type.getRawClass())) { + if (type.isTypeOrSubTypeOf(RangeSet.class)) { return new RangeSetDeserializer(); } if (type.hasRawClass(Range.class)) { diff --git a/guava/src/main/java/com/fasterxml/jackson/datatype/guava/GuavaSerializers.java b/guava/src/main/java/com/fasterxml/jackson/datatype/guava/GuavaSerializers.java index 6b795227..9e9165b9 100644 --- a/guava/src/main/java/com/fasterxml/jackson/datatype/guava/GuavaSerializers.java +++ b/guava/src/main/java/com/fasterxml/jackson/datatype/guava/GuavaSerializers.java @@ -47,7 +47,7 @@ public Iterable convert(Object value) { } @Override - public JsonSerializer findReferenceSerializer(SerializationConfig config, + public JsonSerializer findReferenceSerializer(SerializationConfig config, ReferenceType refType, BeanDescription beanDesc, TypeSerializer contentTypeSerializer, JsonSerializer contentValueSerializer) { @@ -64,32 +64,28 @@ public JsonSerializer findReferenceSerializer(SerializationConfig config, @Override public JsonSerializer findSerializer(SerializationConfig config, JavaType type, BeanDescription beanDesc) { - Class raw = type.getRawClass(); - if (RangeSet.class.isAssignableFrom(raw)) { + if (type.isTypeOrSubTypeOf(RangeSet.class)) { return new RangeSetSerializer(); } - if (Range.class.isAssignableFrom(raw)) { + if (type.isTypeOrSubTypeOf(Range.class)) { return new RangeSerializer(_findDeclared(type, Range.class)); } - if (Table.class.isAssignableFrom(raw)) { - return new TableSerializer(_findDeclared(type, Table.class)); - } // since 2.4 - if (HostAndPort.class.isAssignableFrom(raw)) { + if (type.isTypeOrSubTypeOf(HostAndPort.class)) { return ToStringSerializer.instance; } - if (InternetDomainName.class.isAssignableFrom(raw)) { + if (type.isTypeOrSubTypeOf(InternetDomainName.class)) { return ToStringSerializer.instance; } // not sure how useful, but why not? - if (CacheBuilderSpec.class.isAssignableFrom(raw) || CacheBuilder.class.isAssignableFrom(raw)) { + if (type.isTypeOrSubTypeOf(CacheBuilderSpec.class) || type.isTypeOrSubTypeOf(CacheBuilder.class)) { return ToStringSerializer.instance; } - if (HashCode.class.isAssignableFrom(raw)) { + if (type.isTypeOrSubTypeOf(HashCode.class)) { return ToStringSerializer.instance; } - if (FluentIterable.class.isAssignableFrom(raw)) { + if (type.isTypeOrSubTypeOf(FluentIterable.class)) { JavaType iterableType = _findDeclared(type, Iterable.class); return new StdDelegatingSerializer(FluentConverter.instance, iterableType, null); } @@ -101,7 +97,7 @@ public JsonSerializer findMapLikeSerializer(SerializationConfig config, MapLikeType type, BeanDescription beanDesc, JsonSerializer keySerializer, TypeSerializer elementTypeSerializer, JsonSerializer elementValueSerializer) { - if (Multimap.class.isAssignableFrom(type.getRawClass())) { + if (type.isTypeOrSubTypeOf(Multimap.class)) { final AnnotationIntrospector intr = config.getAnnotationIntrospector(); Object filterId = intr.findFilterId((Annotated)beanDesc.getClassInfo()); JsonIgnoreProperties.Value ignorals = config.getDefaultPropertyIgnorals(Multimap.class, @@ -110,7 +106,7 @@ public JsonSerializer findMapLikeSerializer(SerializationConfig config, return new MultimapSerializer(type, beanDesc, keySerializer, elementTypeSerializer, elementValueSerializer, ignored, filterId); } - if (Cache.class.isAssignableFrom(type.getRawClass())) { + if (type.isTypeOrSubTypeOf(Cache.class)) { final AnnotationIntrospector intr = config.getAnnotationIntrospector(); Object filterId = intr.findFilterId((Annotated)beanDesc.getClassInfo()); JsonIgnoreProperties.Value ignorals = config.getDefaultPropertyIgnorals(Cache.class, @@ -119,6 +115,9 @@ public JsonSerializer findMapLikeSerializer(SerializationConfig config, return new CacheSerializer(type, beanDesc, keySerializer, elementTypeSerializer, elementValueSerializer, ignored, filterId); } + if (type.isTypeOrSubTypeOf(Table.class)) { + return new TableSerializer(type); + } return null; } diff --git a/guava/src/main/java/com/fasterxml/jackson/datatype/guava/GuavaTypeModifier.java b/guava/src/main/java/com/fasterxml/jackson/datatype/guava/GuavaTypeModifier.java index 4f50106d..64439aee 100644 --- a/guava/src/main/java/com/fasterxml/jackson/datatype/guava/GuavaTypeModifier.java +++ b/guava/src/main/java/com/fasterxml/jackson/datatype/guava/GuavaTypeModifier.java @@ -53,6 +53,19 @@ public JavaType modifyType(JavaType type, Type jdkType, TypeBindings bindings, T if (raw == Optional.class) { return ReferenceType.upgradeFrom(type, type.containedTypeOrUnknown(0)); } + if (raw == Table.class) { + MapLikeType columnValueType = + MapLikeType.upgradeFrom( + type, + type.containedTypeOrUnknown(1), + type.containedTypeOrUnknown(2) + ); + return MapLikeType.upgradeFrom( + type, + type.containedTypeOrUnknown(0), + columnValueType + ); + } return type; } } diff --git a/guava/src/main/java/com/fasterxml/jackson/datatype/guava/deser/GuavaCollectionDeserializer.java b/guava/src/main/java/com/fasterxml/jackson/datatype/guava/deser/GuavaCollectionDeserializer.java index d7411364..b341fb91 100644 --- a/guava/src/main/java/com/fasterxml/jackson/datatype/guava/deser/GuavaCollectionDeserializer.java +++ b/guava/src/main/java/com/fasterxml/jackson/datatype/guava/deser/GuavaCollectionDeserializer.java @@ -172,7 +172,7 @@ protected T _deserializeFromSingleValue(JsonParser p, DeserializationContext ctx { final JsonDeserializer valueDes = _valueDeserializer; final TypeDeserializer typeDeser = _valueTypeDeserializer; - final JsonToken t = p.getCurrentToken(); + final JsonToken t = p.currentToken(); final Object value; diff --git a/guava/src/main/java/com/fasterxml/jackson/datatype/guava/deser/GuavaImmutableMapDeserializer.java b/guava/src/main/java/com/fasterxml/jackson/datatype/guava/deser/GuavaImmutableMapDeserializer.java index 716c9bac..4dd6cfff 100644 --- a/guava/src/main/java/com/fasterxml/jackson/datatype/guava/deser/GuavaImmutableMapDeserializer.java +++ b/guava/src/main/java/com/fasterxml/jackson/datatype/guava/deser/GuavaImmutableMapDeserializer.java @@ -43,7 +43,7 @@ protected T _deserializeEntries(JsonParser p, DeserializationContext ctxt) final TypeDeserializer typeDeser = _valueTypeDeserializer; ImmutableMap.Builder builder = createBuilder(); - for (; p.getCurrentToken() == JsonToken.FIELD_NAME; p.nextToken()) { + for (; p.currentToken() == JsonToken.FIELD_NAME; p.nextToken()) { // Must point to field name now String fieldName = p.currentName(); Object key = (keyDes == null) ? fieldName : keyDes.deserializeKey(fieldName, ctxt); diff --git a/guava/src/main/java/com/fasterxml/jackson/datatype/guava/deser/GuavaMapDeserializer.java b/guava/src/main/java/com/fasterxml/jackson/datatype/guava/deser/GuavaMapDeserializer.java index 6ff4d357..3fa66e3a 100644 --- a/guava/src/main/java/com/fasterxml/jackson/datatype/guava/deser/GuavaMapDeserializer.java +++ b/guava/src/main/java/com/fasterxml/jackson/datatype/guava/deser/GuavaMapDeserializer.java @@ -158,7 +158,7 @@ public T deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { // Ok: must point to START_OBJECT or FIELD_NAME - JsonToken t = p.getCurrentToken(); + JsonToken t = p.currentToken(); if (t == JsonToken.START_OBJECT) { // If START_OBJECT, move to next; may also be END_OBJECT t = p.nextToken(); } diff --git a/guava/src/main/java/com/fasterxml/jackson/datatype/guava/deser/cache/GuavaCacheDeserializer.java b/guava/src/main/java/com/fasterxml/jackson/datatype/guava/deser/cache/GuavaCacheDeserializer.java index a54c5dff..1977e742 100644 --- a/guava/src/main/java/com/fasterxml/jackson/datatype/guava/deser/cache/GuavaCacheDeserializer.java +++ b/guava/src/main/java/com/fasterxml/jackson/datatype/guava/deser/cache/GuavaCacheDeserializer.java @@ -160,7 +160,7 @@ private T deserializeContents(JsonParser p, DeserializationContext ctxt) } private void expect(JsonParser p, JsonToken token) throws IOException { - if (p.getCurrentToken() != token) { + if (p.currentToken() != token) { throw new JsonMappingException(p, "Expecting " + token + " to start `Cache` value, found " + p.currentToken(), p.currentLocation()); } diff --git a/guava/src/main/java/com/fasterxml/jackson/datatype/guava/deser/multimap/GuavaMultimapDeserializer.java b/guava/src/main/java/com/fasterxml/jackson/datatype/guava/deser/multimap/GuavaMultimapDeserializer.java index 77cd8c55..ae131df5 100644 --- a/guava/src/main/java/com/fasterxml/jackson/datatype/guava/deser/multimap/GuavaMultimapDeserializer.java +++ b/guava/src/main/java/com/fasterxml/jackson/datatype/guava/deser/multimap/GuavaMultimapDeserializer.java @@ -272,7 +272,7 @@ private Object getCurrentTokenValue(JsonParser p, DeserializationContext ctxt) } private void expect(JsonParser p, JsonToken token) throws IOException { - if (p.getCurrentToken() != token) { + if (p.currentToken() != token) { throw new JsonMappingException(p, "Expecting " + token + " to start `MultiMap` value, found " + p.currentToken(), p.currentLocation()); } diff --git a/guava/src/main/java/com/fasterxml/jackson/datatype/guava/deser/table/HashBasedTableDeserializer.java b/guava/src/main/java/com/fasterxml/jackson/datatype/guava/deser/table/HashBasedTableDeserializer.java new file mode 100644 index 00000000..b9fb8185 --- /dev/null +++ b/guava/src/main/java/com/fasterxml/jackson/datatype/guava/deser/table/HashBasedTableDeserializer.java @@ -0,0 +1,44 @@ +package com.fasterxml.jackson.datatype.guava.deser.table; + +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.KeyDeserializer; +import com.fasterxml.jackson.databind.deser.NullValueProvider; +import com.fasterxml.jackson.databind.jsontype.TypeDeserializer; +import com.fasterxml.jackson.databind.type.MapLikeType; +import com.google.common.collect.HashBasedTable; + +/** + * Provides deserialization for the Guava HashBasedTable class. + * + * @author Abhishekkr3003 + */ +public class HashBasedTableDeserializer + extends MutableTableDeserializer> { + private static final long serialVersionUID = 1L; + + public HashBasedTableDeserializer(MapLikeType type) { + super(type); + } + + public HashBasedTableDeserializer(MapLikeType type, KeyDeserializer rowDeserializer, + KeyDeserializer columnDeserializer, TypeDeserializer elementTypeDeserializer, + JsonDeserializer elementDeserializer, NullValueProvider nvp) { + super(type, rowDeserializer, columnDeserializer, elementTypeDeserializer, + elementDeserializer, nvp + ); + } + + @Override + protected HashBasedTable createTable() { + return HashBasedTable.create(); + } + + @Override + protected JsonDeserializer _createContextual(MapLikeType type, + KeyDeserializer rowDeserializer, + KeyDeserializer columnDeserializer, TypeDeserializer typeDeserializer, + JsonDeserializer elementDeserializer, NullValueProvider nvp) { + return new HashBasedTableDeserializer(type, + rowDeserializer, columnDeserializer, typeDeserializer, elementDeserializer, nvp); + } +} diff --git a/guava/src/main/java/com/fasterxml/jackson/datatype/guava/deser/table/ImmutableTableDeserializer.java b/guava/src/main/java/com/fasterxml/jackson/datatype/guava/deser/table/ImmutableTableDeserializer.java new file mode 100644 index 00000000..dedd7c54 --- /dev/null +++ b/guava/src/main/java/com/fasterxml/jackson/datatype/guava/deser/table/ImmutableTableDeserializer.java @@ -0,0 +1,115 @@ +package com.fasterxml.jackson.datatype.guava.deser.table; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.KeyDeserializer; +import com.fasterxml.jackson.databind.deser.NullValueProvider; +import com.fasterxml.jackson.databind.jsontype.TypeDeserializer; +import com.fasterxml.jackson.databind.type.MapLikeType; +import com.fasterxml.jackson.databind.util.AccessPattern; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableTable; +import java.io.IOException; + +/** + * Provides deserialization for the Guava ImmutableTable class. + * + * @author Abhishekkr3003 + */ +public class ImmutableTableDeserializer + extends TableDeserializer> { + private static final long serialVersionUID = 2L; + + public ImmutableTableDeserializer(MapLikeType type) { + super(type); + } + + protected ImmutableTableDeserializer(MapLikeType type, KeyDeserializer rowDeserializer, + KeyDeserializer colDeserializer, JsonDeserializer valueDeserializer, + TypeDeserializer valueTypeDeserializer, NullValueProvider nuller) { + super(type, rowDeserializer, colDeserializer, valueTypeDeserializer, valueDeserializer, + nuller + ); + } + + @Override + public AccessPattern getEmptyAccessPattern() { + // immutable, hence: + return AccessPattern.CONSTANT; + } + + + @Override + public Object getEmptyValue(DeserializationContext ctxt) throws JsonMappingException { + return ImmutableMap.of(); + } + + protected ImmutableTable.Builder createBuilder() { + return ImmutableTable.builder(); + } + + @Override + public ImmutableTable deserialize(JsonParser p, + DeserializationContext ctxt) throws IOException { + ImmutableTable.Builder table = createBuilder(); + + JsonToken currToken = p.currentToken(); + if (currToken != JsonToken.FIELD_NAME && currToken != JsonToken.END_OBJECT) { + expect(p, JsonToken.START_OBJECT); + currToken = p.nextToken(); + } + + for (; currToken == JsonToken.FIELD_NAME; currToken = p.nextToken()) { + final Object rowKey; + if (_rowDeserializer != null) { + rowKey = _rowDeserializer.deserializeKey(p.currentName(), ctxt); + } else { + rowKey = p.currentName(); + } + + p.nextToken(); + expect(p, JsonToken.START_OBJECT); + + for ( + currToken = p.nextToken(); currToken == JsonToken.FIELD_NAME; + currToken = p.nextToken()) { + final Object colKey; + if (_colDeserializer != null) { + colKey = _colDeserializer.deserializeKey(p.currentName(), ctxt); + } else { + colKey = p.currentName(); + } + + p.nextToken(); + + final Object value; + if (p.currentToken() == JsonToken.VALUE_NULL) { + if (_skipNullValues) { + continue; + } + value = _nullProvider.getNullValue(ctxt); + } else if (_valueTypeDeserializer != null) { + value = _valueDeserializer.deserializeWithType(p, ctxt, _valueTypeDeserializer); + } else { + value = _valueDeserializer.deserialize(p, ctxt); + } + table.put(rowKey, colKey, value); + } + expect(p, JsonToken.END_OBJECT); + } + return table.build(); + } + + @Override + protected JsonDeserializer _createContextual(MapLikeType type, + KeyDeserializer rowDeserializer, + KeyDeserializer columnDeserializer, TypeDeserializer valueTypeDeserializer, + JsonDeserializer valueDeserializer, NullValueProvider nullValueProvider) { + return new ImmutableTableDeserializer(type, rowDeserializer, columnDeserializer, + valueDeserializer, valueTypeDeserializer, nullValueProvider + ); + } +} diff --git a/guava/src/main/java/com/fasterxml/jackson/datatype/guava/deser/table/MutableTableDeserializer.java b/guava/src/main/java/com/fasterxml/jackson/datatype/guava/deser/table/MutableTableDeserializer.java new file mode 100644 index 00000000..b829c471 --- /dev/null +++ b/guava/src/main/java/com/fasterxml/jackson/datatype/guava/deser/table/MutableTableDeserializer.java @@ -0,0 +1,91 @@ +package com.fasterxml.jackson.datatype.guava.deser.table; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.KeyDeserializer; +import com.fasterxml.jackson.databind.deser.NullValueProvider; +import com.fasterxml.jackson.databind.jsontype.TypeDeserializer; +import com.fasterxml.jackson.databind.type.MapLikeType; +import com.google.common.collect.Table; +import java.io.IOException; + +/** + * @author Abhishekkr3003 + */ +public abstract class MutableTableDeserializer> + extends TableDeserializer +{ + private static final long serialVersionUID = 1L; + + protected MutableTableDeserializer(MapLikeType type) { + super(type); + } + + protected MutableTableDeserializer(MapLikeType _type, KeyDeserializer _rowDeserializer, + KeyDeserializer _colDeserializer, TypeDeserializer _valueTypeDeserializer, + JsonDeserializer _valueDeserializer, NullValueProvider nvp) { + super( + _type, _rowDeserializer, _colDeserializer, _valueTypeDeserializer, _valueDeserializer, + nvp + ); + } + + protected abstract T createTable(); + + @Override + protected abstract JsonDeserializer _createContextual(MapLikeType t, KeyDeserializer rkd, + KeyDeserializer ckd, TypeDeserializer vtd, JsonDeserializer vd, NullValueProvider np); + + @Override + public T deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { + T table = createTable(); + + JsonToken currToken = p.currentToken(); + if (currToken != JsonToken.FIELD_NAME && currToken != JsonToken.END_OBJECT) { + expect(p, JsonToken.START_OBJECT); + currToken = p.nextToken(); + } + + for (; currToken == JsonToken.FIELD_NAME; currToken = p.nextToken()) { + final Object rowKey; + if (_rowDeserializer != null) { + rowKey = _rowDeserializer.deserializeKey(p.currentName(), ctxt); + } else { + rowKey = p.currentName(); + } + + p.nextToken(); + expect(p, JsonToken.START_OBJECT); + + for ( + currToken = p.nextToken(); currToken == JsonToken.FIELD_NAME; + currToken = p.nextToken()) { + final Object colKey; + if (_colDeserializer != null) { + colKey = _colDeserializer.deserializeKey(p.currentName(), ctxt); + } else { + colKey = p.currentName(); + } + + p.nextToken(); + + final Object value; + if (p.currentToken() == JsonToken.VALUE_NULL) { + if (_skipNullValues) { + continue; + } + value = _nullProvider.getNullValue(ctxt); + } else if (_valueTypeDeserializer != null) { + value = _valueDeserializer.deserializeWithType(p, ctxt, _valueTypeDeserializer); + } else { + value = _valueDeserializer.deserialize(p, ctxt); + } + table.put(rowKey, colKey, value); + } + expect(p, JsonToken.END_OBJECT); + } + return table; + } +} diff --git a/guava/src/main/java/com/fasterxml/jackson/datatype/guava/deser/table/TableDeserializer.java b/guava/src/main/java/com/fasterxml/jackson/datatype/guava/deser/table/TableDeserializer.java new file mode 100644 index 00000000..763960d1 --- /dev/null +++ b/guava/src/main/java/com/fasterxml/jackson/datatype/guava/deser/table/TableDeserializer.java @@ -0,0 +1,115 @@ +package com.fasterxml.jackson.datatype.guava.deser.table; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; +import com.fasterxml.jackson.databind.BeanProperty; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JavaType; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.KeyDeserializer; +import com.fasterxml.jackson.databind.deser.ContextualDeserializer; +import com.fasterxml.jackson.databind.deser.NullValueProvider; +import com.fasterxml.jackson.databind.deser.impl.NullsConstantProvider; +import com.fasterxml.jackson.databind.deser.std.StdDeserializer; +import com.fasterxml.jackson.databind.jsontype.TypeDeserializer; +import com.fasterxml.jackson.databind.type.MapLikeType; +import com.google.common.collect.Table; +import java.io.IOException; + +/** + * @author Abhishekkr3003 + */ +public abstract class TableDeserializer> + extends StdDeserializer implements ContextualDeserializer { + private static final long serialVersionUID = 1L; + + protected final MapLikeType _type; + protected final KeyDeserializer _rowDeserializer; + protected final KeyDeserializer _colDeserializer; + protected final TypeDeserializer _valueTypeDeserializer; + protected final JsonDeserializer _valueDeserializer; + + // since 2.9.5: in 3.x demote to `ContainerDeserializerBase` + protected final NullValueProvider _nullProvider; + protected final boolean _skipNullValues; + + protected TableDeserializer(MapLikeType _type, KeyDeserializer _rowDeserializer, + KeyDeserializer _colDeserializer, TypeDeserializer _valueTypeDeserializer, + JsonDeserializer _valueDeserializer) { + this( + _type, _rowDeserializer, _colDeserializer, _valueTypeDeserializer, _valueDeserializer, + null + ); + } + + protected TableDeserializer(MapLikeType _type, KeyDeserializer _rowDeserializer, + KeyDeserializer _colDeserializer, TypeDeserializer _valueTypeDeserializer, + JsonDeserializer _valueDeserializer, NullValueProvider nvp) { + super(_type); + this._type = _type; + this._rowDeserializer = _rowDeserializer; + this._colDeserializer = _colDeserializer; + this._valueTypeDeserializer = _valueTypeDeserializer; + this._valueDeserializer = _valueDeserializer; + this._nullProvider = nvp; + _skipNullValues = (nvp == null) ? false : NullsConstantProvider.isSkipper(nvp); + } + + protected TableDeserializer(MapLikeType type) { + super(type); + _type = type; + _rowDeserializer = null; + _colDeserializer = null; + _valueDeserializer = null; + _valueTypeDeserializer = null; + _nullProvider = null; + _skipNullValues = false; + } + + /** + * We need to use this method to properly handle possible contextual variants of key and value + * deserializers, as well as type deserializers. + */ + @Override + public JsonDeserializer createContextual(DeserializationContext ctxt, + BeanProperty property) throws JsonMappingException { + KeyDeserializer rkd = _rowDeserializer; + if (rkd == null) { + rkd = ctxt.findKeyDeserializer(_type.getKeyType(), property); + } + MapLikeType columnValueType = (MapLikeType) _type.getContentType(); + KeyDeserializer ckd = _colDeserializer; + if (ckd == null) { + ckd = ctxt.findKeyDeserializer(columnValueType.getKeyType(), property); + } + JsonDeserializer valueDeser = _valueDeserializer; + final JavaType vt = columnValueType.getContentType(); + if (valueDeser == null) { + valueDeser = ctxt.findContextualValueDeserializer(vt, property); + } else { // if directly assigned, probably not yet contextual, so: + valueDeser = ctxt.handleSecondaryContextualization(valueDeser, property, vt); + } + // Type deserializer is slightly different; must be passed, but needs to become contextual: + TypeDeserializer vtd = _valueTypeDeserializer; + if (vtd != null) { + vtd = vtd.forProperty(property); + } + return _createContextual(_type, + rkd, ckd, vtd, valueDeser, findContentNullProvider(ctxt, property, valueDeser)); + } + + protected abstract JsonDeserializer _createContextual(MapLikeType t, KeyDeserializer rkd, + KeyDeserializer ckd, TypeDeserializer vtd, JsonDeserializer vd, NullValueProvider np); + + + protected void expect(JsonParser p, JsonToken token) throws IOException { + if (p.currentToken() != token) { + throw new JsonMappingException( + p, + "Expecting " + token + " to start `TABLE` value, found " + p.currentToken(), + p.currentLocation() + ); + } + } +} diff --git a/guava/src/main/java/com/fasterxml/jackson/datatype/guava/deser/table/TreeBasedTableDeserializer.java b/guava/src/main/java/com/fasterxml/jackson/datatype/guava/deser/table/TreeBasedTableDeserializer.java new file mode 100644 index 00000000..c102a183 --- /dev/null +++ b/guava/src/main/java/com/fasterxml/jackson/datatype/guava/deser/table/TreeBasedTableDeserializer.java @@ -0,0 +1,46 @@ +package com.fasterxml.jackson.datatype.guava.deser.table; + +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.KeyDeserializer; +import com.fasterxml.jackson.databind.deser.NullValueProvider; +import com.fasterxml.jackson.databind.jsontype.TypeDeserializer; +import com.fasterxml.jackson.databind.type.MapLikeType; +import com.google.common.collect.TreeBasedTable; + +/** + * Provides deserialization for the Guava TreeBasedTable class. + * + * @author Abhishekkr3003 + */ +public class TreeBasedTableDeserializer + extends MutableTableDeserializer> { + private static final long serialVersionUID = 1L; + + public TreeBasedTableDeserializer(MapLikeType type) { + super(type); + } + + public TreeBasedTableDeserializer(MapLikeType type, KeyDeserializer rowDeserializer, + KeyDeserializer columnDeserializer, TypeDeserializer elementTypeDeserializer, + JsonDeserializer elementDeserializer, NullValueProvider nvp) { + super(type, rowDeserializer, columnDeserializer, elementTypeDeserializer, + elementDeserializer, nvp + ); + } + + @SuppressWarnings("unchecked") + @Override + protected TreeBasedTable createTable() { + TreeBasedTable naturalOrder = TreeBasedTable.create(); + return (TreeBasedTable) naturalOrder; + } + + @Override + protected JsonDeserializer _createContextual(MapLikeType type, + KeyDeserializer rowDeserializer, + KeyDeserializer columnDeserializer, TypeDeserializer typeDeserializer, + JsonDeserializer elementDeserializer, NullValueProvider nvp) { + return new TreeBasedTableDeserializer( + type, rowDeserializer, columnDeserializer, typeDeserializer, elementDeserializer, nvp); + } +} diff --git a/guava/src/main/java/com/fasterxml/jackson/datatype/guava/ser/TableSerializer.java b/guava/src/main/java/com/fasterxml/jackson/datatype/guava/ser/TableSerializer.java index b02434c3..5558d29c 100644 --- a/guava/src/main/java/com/fasterxml/jackson/datatype/guava/ser/TableSerializer.java +++ b/guava/src/main/java/com/fasterxml/jackson/datatype/guava/ser/TableSerializer.java @@ -11,8 +11,10 @@ import com.fasterxml.jackson.databind.ser.ContainerSerializer; import com.fasterxml.jackson.databind.ser.ContextualSerializer; import com.fasterxml.jackson.databind.ser.std.MapSerializer; +import com.fasterxml.jackson.databind.type.MapLikeType; import com.fasterxml.jackson.databind.type.MapType; import com.fasterxml.jackson.databind.type.TypeFactory; + import com.google.common.collect.Table; /** @@ -28,7 +30,7 @@ public class TableSerializer * Type declaration that defines parameters; may be a supertype of actual * type of property being serialized. */ - private final JavaType _type; + private final MapLikeType _type; private final BeanProperty _property; @@ -45,7 +47,7 @@ public class TableSerializer /********************************************************** */ - public TableSerializer(final JavaType type) + public TableSerializer(final MapLikeType type) { super(type); _type = type; @@ -75,10 +77,11 @@ protected TableSerializer(final TableSerializer src, _valueTypeSerializer = valueTypeSerializer; _valueSerializer = (JsonSerializer) valueSerializer; + final MapLikeType columnValueType = (MapLikeType) _type.getContentType(); final MapType columnAndValueType = typeFactory.constructMapType(Map.class, - _type.containedTypeOrUnknown(1), _type.containedTypeOrUnknown(2)); + columnValueType.getKeyType(), columnValueType.getContentType()); - JsonSerializer columnAndValueSerializer = + JsonSerializer columnAndValueSerializer = MapSerializer.construct((Set) null, columnAndValueType, false, @@ -88,7 +91,7 @@ protected TableSerializer(final TableSerializer src, null); final MapType rowMapType = typeFactory.constructMapType(Map.class, - _type.containedTypeOrUnknown(0), columnAndValueType); + columnValueType.getKeyType(), columnAndValueType); _rowMapSerializer = MapSerializer.construct((Set) null, rowMapType, @@ -134,8 +137,9 @@ protected ContainerSerializer _withValueTypeSerializer(final TypeSerializer t public JsonSerializer createContextual(final SerializerProvider provider, final BeanProperty property ) throws JsonMappingException { JsonSerializer valueSer = _valueSerializer; + MapLikeType columnValueType = (MapLikeType) _type.getContentType(); if (valueSer == null) { // if type is final, can actually resolve: - final JavaType valueType = _type.containedTypeOrUnknown(2); + final JavaType valueType = columnValueType.getContentType(); if (valueType.isFinal()) { valueSer = provider.findValueSerializer(valueType, property); } @@ -145,14 +149,14 @@ else if (valueSer instanceof ContextualSerializer) { } JsonSerializer rowKeySer = _rowSerializer; if (rowKeySer == null) { - rowKeySer = provider.findKeySerializer(_type.containedTypeOrUnknown(0), property); + rowKeySer = provider.findKeySerializer(_type.getKeyType(), property); } else if (rowKeySer instanceof ContextualSerializer) { rowKeySer = ((ContextualSerializer) rowKeySer).createContextual(provider, property); } JsonSerializer columnKeySer = _columnSerializer; if (columnKeySer == null) { - columnKeySer = provider.findKeySerializer(_type.containedTypeOrUnknown(1), property); + columnKeySer = provider.findKeySerializer(columnValueType.getKeyType(), property); } else if (columnKeySer instanceof ContextualSerializer) { columnKeySer = ((ContextualSerializer) columnKeySer).createContextual(provider, property); @@ -173,7 +177,7 @@ else if (columnKeySer instanceof ContextualSerializer) { @Override public JavaType getContentType() { - return _type.getContentType(); + return _type.getContentType().getContentType(); } @Override diff --git a/guava/src/test/java/com/fasterxml/jackson/datatype/guava/TableSerializationTest.java b/guava/src/test/java/com/fasterxml/jackson/datatype/guava/TableSerializationTest.java deleted file mode 100644 index 48354dec..00000000 --- a/guava/src/test/java/com/fasterxml/jackson/datatype/guava/TableSerializationTest.java +++ /dev/null @@ -1,164 +0,0 @@ -package com.fasterxml.jackson.datatype.guava; - -import java.io.IOException; - -import com.fasterxml.jackson.core.JsonGenerator; -import com.fasterxml.jackson.core.type.TypeReference; - -import com.fasterxml.jackson.databind.*; -import com.fasterxml.jackson.databind.module.SimpleModule; - -import com.google.common.collect.ImmutableTable; - -public class TableSerializationTest extends ModuleTestBase -{ - private final ObjectMapper MAPPER = mapperWithModule(false); - { - MAPPER.registerModule(new ComplexKeyModule()); - } - - static class ComplexKeyModule extends SimpleModule - { - private static final long serialVersionUID = 1L; - - public ComplexKeyModule() - { - this.addKeySerializer(ComplexKey.class, new JsonSerializer() { - @Override - public void serialize( final ComplexKey value, final JsonGenerator g, final SerializerProvider provider ) - throws IOException - { - g.writeFieldName(value.getKey1() + ":" + value.getKey2()); - } - }); - - this.addKeyDeserializer(ComplexKey.class, new KeyDeserializer() { - @Override - public Object deserializeKey( final String key, final DeserializationContext ctxt ) throws IOException - { - final String[] split = key.split(":"); - return new ComplexKey(split[0], split[1]); - } - }); - } - } - - static class ComplexKey - { - private String key1; - private String key2; - - public ComplexKey( final String key1, final String key2 ) - { - super(); - this.key1 = key1; - this.key2 = key2; - } - - public String getKey1() - { - return this.key1; - } - - public void setKey1( final String key1 ) - { - this.key1 = key1; - } - - public String getKey2() - { - return this.key2; - } - - public void setKey2( final String key2 ) - { - this.key2 = key2; - } - - @Override - public int hashCode() - { - final int prime = 31; - int result = 1; - result = prime * result + ((this.key1 == null) ? 0 : this.key1.hashCode()); - result = prime * result + ((this.key2 == null) ? 0 : this.key2.hashCode()); - return result; - } - - @Override - public boolean equals( final Object obj ) - { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if ( !(obj instanceof ComplexKey)) { - return false; - } - final ComplexKey other = (ComplexKey) obj; - if (this.key1 == null) { - if (other.key1 != null) { - return false; - } - } - else if ( !this.key1.equals(other.key1)) { - return false; - } - if (this.key2 == null) { - if (other.key2 != null) { - return false; - } - } - else if ( !this.key2.equals(other.key2)) { - return false; - } - return true; - } - - } - - public void testSimpleKeyImmutableTableSerde() throws IOException - { - final ImmutableTable.Builder builder = ImmutableTable.builder(); - builder.put(Integer.valueOf(42), "column42", "some value 42"); - builder.put(Integer.valueOf(45), "column45", "some value 45"); - final ImmutableTable simpleTable = builder.build(); - - final String simpleJson = MAPPER.writeValueAsString(simpleTable); - assertEquals("{\"42\":{\"column42\":\"some value 42\"},\"45\":{\"column45\":\"some value 45\"}}", simpleJson); - - // !!! TODO: support deser - - /* - final ImmutableTable reconstitutedTable = - this.MAPPER.readValue(simpleJson, new TypeReference>() {}); - assertEquals(simpleTable, reconstitutedTable); - */ - } - - /** - * This test illustrates one way to use objects as keys in Tables. - */ - public void testComplexKeyImmutableTableSerde() throws IOException - { - final ImmutableTable.Builder ckBuilder = ImmutableTable.builder(); - ckBuilder.put(Integer.valueOf(42), new ComplexKey("field1", "field2"), "some value 42"); - ckBuilder.put(Integer.valueOf(45), new ComplexKey("field1", "field2"), "some value 45"); - final ImmutableTable complexKeyTable = ckBuilder.build(); - - final TypeReference> tableType = new TypeReference>() - {}; - - final String ckJson = this.MAPPER.writerFor(tableType).writeValueAsString(complexKeyTable); - assertEquals("{\"42\":{\"field1:field2\":\"some value 42\"},\"45\":{\"field1:field2\":\"some value 45\"}}", ckJson); - - // !!! TODO: support deser -/* - - final ImmutableTable reconstitutedTable = this.MAPPER.readValue(ckJson, tableType); - assertEquals(complexKeyTable, reconstitutedTable); - */ - } -} diff --git a/guava/src/test/java/com/fasterxml/jackson/datatype/guava/TableTest.java b/guava/src/test/java/com/fasterxml/jackson/datatype/guava/TableTest.java new file mode 100644 index 00000000..1172f5d5 --- /dev/null +++ b/guava/src/test/java/com/fasterxml/jackson/datatype/guava/TableTest.java @@ -0,0 +1,232 @@ +package com.fasterxml.jackson.datatype.guava; + +import java.io.IOException; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.type.TypeReference; + +import com.fasterxml.jackson.databind.*; +import com.fasterxml.jackson.databind.module.SimpleModule; + +import com.google.common.collect.HashBasedTable; +import com.google.common.collect.ImmutableTable; +import com.google.common.collect.TreeBasedTable; + +public class TableTest extends ModuleTestBase +{ + private final ObjectMapper MAPPER = builderWithModule(false) + .addModule(new ComplexKeyModule()) + .build(); + + static class ComplexKeyModule extends SimpleModule + { + private static final long serialVersionUID = 1L; + + public ComplexKeyModule() + { + this.addKeySerializer(ComplexKey.class, new JsonSerializer() { + @Override + public void serialize( final ComplexKey value, final JsonGenerator g, final SerializerProvider provider ) + throws IOException + { + g.writeFieldName(value.getKey1() + ":" + value.getKey2()); + } + }); + + this.addKeyDeserializer(ComplexKey.class, new KeyDeserializer() { + @Override + public Object deserializeKey( final String key, final DeserializationContext ctxt ) throws IOException + { + final String[] split = key.split(":"); + return new ComplexKey(split[0], split[1]); + } + }); + } + } + + static class ComplexKey implements Comparable + { + private String key1; + private String key2; + + public ComplexKey( final String key1, final String key2 ) + { + super(); + this.key1 = key1; + this.key2 = key2; + } + + public String getKey1() + { + return this.key1; + } + + public void setKey1( final String key1 ) + { + this.key1 = key1; + } + + public String getKey2() + { + return this.key2; + } + + public void setKey2( final String key2 ) + { + this.key2 = key2; + } + + @Override + public int hashCode() + { + final int prime = 31; + int result = 1; + result = prime * result + ((this.key1 == null) ? 0 : this.key1.hashCode()); + result = prime * result + ((this.key2 == null) ? 0 : this.key2.hashCode()); + return result; + } + + @Override + public boolean equals( final Object obj ) + { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if ( !(obj instanceof ComplexKey)) { + return false; + } + final ComplexKey other = (ComplexKey) obj; + if (this.key1 == null) { + if (other.key1 != null) { + return false; + } + } + else if ( !this.key1.equals(other.key1)) { + return false; + } + if (this.key2 == null) { + if (other.key2 != null) { + return false; + } + } + else if ( !this.key2.equals(other.key2)) { + return false; + } + return true; + } + + @Override + public int compareTo(ComplexKey complexKey) { + return this.key1.compareTo(complexKey.getKey1()); + } + } + + public void testSimpleKeyImmutableTableSerde() throws IOException + { + final ImmutableTable.Builder builder = ImmutableTable.builder(); + builder.put(Integer.valueOf(42), "column42", "some value 42"); + builder.put(Integer.valueOf(45), "column45", "some value 45"); + + ImmutableTable simpleTable = builder.build(); + + final String simpleJson = MAPPER.writeValueAsString(simpleTable); + assertEquals("{\"42\":{\"column42\":\"some value 42\"},\"45\":{\"column45\":\"some value 45\"}}", simpleJson); + + final ImmutableTable reconstitutedTable = + this.MAPPER.readValue(simpleJson, + new TypeReference>() { + } + ); + assertEquals(simpleTable, reconstitutedTable); + } + + public void testSimpleKeyHashBasedTableSerde() throws IOException + { + final HashBasedTable simpleTable = HashBasedTable.create(); + simpleTable.put(Integer.valueOf(42), "column42", "some value 42"); + simpleTable.put(Integer.valueOf(45), "column45", "some value 45"); + + final String simpleJson = MAPPER.writeValueAsString(simpleTable); + assertEquals("{\"42\":{\"column42\":\"some value 42\"},\"45\":{\"column45\":\"some value 45\"}}", simpleJson); + + final HashBasedTable reconstitutedTable = + this.MAPPER.readValue(simpleJson, + new TypeReference>() { + } + ); + assertEquals(simpleTable, reconstitutedTable); + } + + public void testSimpleKeyTreeBasedTableSerde() throws IOException + { + final TreeBasedTable simpleTable = TreeBasedTable.create(); + simpleTable.put(Integer.valueOf(42), "column42", "some value 42"); + simpleTable.put(Integer.valueOf(45), "column45", "some value 45"); + + final String simpleJson = MAPPER.writeValueAsString(simpleTable); + assertEquals("{\"42\":{\"column42\":\"some value 42\"},\"45\":{\"column45\":\"some value 45\"}}", simpleJson); + + final TreeBasedTable reconstitutedTable = + this.MAPPER.readValue(simpleJson, + new TypeReference>() { + } + ); + assertEquals(simpleTable, reconstitutedTable); + } + + /** + * This test illustrates one way to use objects as keys in Tables. + */ + public void testComplexKeyImmutableTableSerde() throws IOException + { + final ImmutableTable.Builder builder = ImmutableTable.builder(); + builder.put(Integer.valueOf(42), new ComplexKey("field1", "field2"), "some value 42"); + builder.put(Integer.valueOf(45), new ComplexKey("field1", "field2"), "some value 45"); + + ImmutableTable complexKeyTable = builder.build(); + + final TypeReference> tableType = new TypeReference>() + {}; + + final String ckJson = this.MAPPER.writerFor(tableType).writeValueAsString(complexKeyTable); + assertEquals("{\"42\":{\"field1:field2\":\"some value 42\"},\"45\":{\"field1:field2\":\"some value 45\"}}", ckJson); + + final ImmutableTable reconstitutedTable = this.MAPPER.readValue(ckJson, tableType); + assertEquals(complexKeyTable, reconstitutedTable); + } + + public void testComplexKeyHashBasedTableSerde() throws IOException + { + final HashBasedTable complexKeyTable = HashBasedTable.create(); + complexKeyTable.put(Integer.valueOf(42), new ComplexKey("field1", "field2"), "some value 42"); + complexKeyTable.put(Integer.valueOf(45), new ComplexKey("field1", "field2"), "some value 45"); + + final TypeReference> tableType = new TypeReference>() + {}; + + final String ckJson = this.MAPPER.writerFor(tableType).writeValueAsString(complexKeyTable); + assertEquals("{\"42\":{\"field1:field2\":\"some value 42\"},\"45\":{\"field1:field2\":\"some value 45\"}}", ckJson); + + final HashBasedTable reconstitutedTable = this.MAPPER.readValue(ckJson, tableType); + assertEquals(complexKeyTable, reconstitutedTable); + } + + public void testComplexKeyTreeTableSerde() throws IOException + { + final TreeBasedTable complexKeyTable = TreeBasedTable.create(); + complexKeyTable.put(Integer.valueOf(42), new ComplexKey("field1", "field2"), "some value 42"); + complexKeyTable.put(Integer.valueOf(45), new ComplexKey("field1", "field2"), "some value 45"); + + final TypeReference> tableType = new TypeReference>() + {}; + + final String ckJson = this.MAPPER.writerFor(tableType).writeValueAsString(complexKeyTable); + assertEquals("{\"42\":{\"field1:field2\":\"some value 42\"},\"45\":{\"field1:field2\":\"some value 45\"}}", ckJson); + + final TreeBasedTable reconstitutedTable = this.MAPPER.readValue(ckJson, tableType); + assertEquals(complexKeyTable, reconstitutedTable); + } +} diff --git a/release-notes/CREDITS-2.x b/release-notes/CREDITS-2.x index 712652a5..9581ca82 100644 --- a/release-notes/CREDITS-2.x +++ b/release-notes/CREDITS-2.x @@ -133,3 +133,7 @@ Arthur Chan (@arthurscchan) * Contributed #138: (guava) `GuavaCollectionDeserializer` still throws NPE in some circumstances (2.17.0) + +Abhishek Kumar (@Abhishekkr3003) + * Contributed #1: (guava) Add deserialization support for `Table` + (2.19.0) diff --git a/release-notes/VERSION-2.x b/release-notes/VERSION-2.x index 3d90846f..f3407313 100644 --- a/release-notes/VERSION-2.x +++ b/release-notes/VERSION-2.x @@ -18,7 +18,8 @@ Active Maintainers: 2.19.0 (not yet released) -- +#1: (guava) Add deserialization support for `Table` + (contributed by Abhishek K) 2.18.1 (28-Oct-2024)