Skip to content

Add deserialization support for Table<R, C, V> #163

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

Merged
merged 8 commits into from
Nov 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -259,8 +262,15 @@ public JsonDeserializer<?> findMapLikeDeserializer(MapLikeType type,
}

if (Table.class.isAssignableFrom(raw)) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@cowtowncoder, should we use MapLikeType/MapLikeDeserializer or not? What I found is if we keep it MapLikeType, then we have to use Type something like Map<R, Map<C, V>>, and then we'll have KeyDeserializer for RowKey and JsonDeserializer for ColumnValueMap, and then while deserializing the Table<R, C, V> we'll have to deserialize Map<C, V> first and then have to put each C, V in Table<R, C, V> for R, which is inefficient in my opinion. Contrarily, if we don't use MapLikeType and keep it JavaType (Using Bean Deserializer), we can deserialize directly to Table<R, C, V>.

What do you think about this?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmmmh. Tough question. I think that:

  1. Type should be retained as MapLikeType (to help Jackson databind be somewhat aware of type's semantic)
  2. But! We do NOT need to use MapLikeDeserializer, if that is possible

If (2) can't be done for some reason (I may be overlooking something), I'm ok in change to make Tables not be considered "Map-like". This will add some work on (de)serialization handling, possibly.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, I have used MapLikeType without using any other MapLikeType deserialization arguments, as if we use elementDeserializer, then we might have to first deserialize to Map<C, V> and then have to put in Table<R,C,V>, since we cannot get C's deserializer and V's deserializer separately from JsonDeserializer<?> elementDeserializer of this API:

public JsonDeserializer<?> findMapLikeDeserializer(MapLikeType type, DeserializationConfig config, BeanDescription beanDesc, KeyDeserializer keyDeserializer, TypeDeserializer elementTypeDeserializer, JsonDeserializer<?> elementDeserializer)

Well, this is from my limited knowledge of this codebase; please do correct me if I'm missing something here.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That sounds good -- hoping to review this in near future, just having bit of overload with everything going on. But thank you again for this contribution!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the update! I completely understand, and no worries at all. Take your time. I’m happy to contribute and look forward to any feedback whenever you’re able to review. Thanks again!

// !!! 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<JsonDeserializer<?>> cacheDeserializer = findCacheDeserializer(raw, type, config,
beanDesc, keyDeserializer, elementTypeDeserializer, elementDeserializer);
Expand Down Expand Up @@ -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)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<Object> contentValueSerializer)
{
Expand All @@ -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);
}
Expand All @@ -101,7 +97,7 @@ public JsonSerializer<?> findMapLikeSerializer(SerializationConfig config,
MapLikeType type, BeanDescription beanDesc, JsonSerializer<Object> keySerializer,
TypeSerializer elementTypeSerializer, JsonSerializer<Object> 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,
Expand All @@ -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,
Expand All @@ -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;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ protected T _deserializeEntries(JsonParser p, DeserializationContext ctxt)
final TypeDeserializer typeDeser = _valueTypeDeserializer;

ImmutableMap.Builder<Object, Object> 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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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());
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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<HashBasedTable<Object, Object, Object>> {
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<Object, Object, Object> 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);
}
}
Original file line number Diff line number Diff line change
@@ -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<ImmutableTable<Object, Object, Object>> {
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<Object, Object, Object> createBuilder() {
return ImmutableTable.builder();
}

@Override
public ImmutableTable<Object, Object, Object> deserialize(JsonParser p,
DeserializationContext ctxt) throws IOException {
ImmutableTable.Builder<Object, Object, Object> 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
);
}
}
Loading
Loading