Skip to content

Latest commit

 

History

History
51 lines (40 loc) · 4.19 KB

MappersAndConverters.md

File metadata and controls

51 lines (40 loc) · 4.19 KB

Main page

Converters, mappers and mapper factories

In order to convert data from MessagePack types to basic Java types (Integer, Double, String and etc.) and back, you need mappers. The mapper contains a stack of converters. For each type , the converter is selected according to the following logic:

  1. The mapper chooses the first matching converter from the top of the stack (the one that was added last).
  2. Check that converter is suitable
    • The source type equals the input type of converter
    • canConvertValue or canConvertObject((depends on converter type see creating your own converter)) should return true
  3. If this converter is not applicable, the mapper checks the next topmost converter after it.

Using built-in mappers

See an example of DefaultMessagePackMapper usage:

DefaultMessagePackMapper mapper = DefaultMessagePackMapperFactory.getInstance().defaultSimpleTypeMapper();
// check default Object converters
assertEquals(ValueFactory.newBoolean(false), mapper.toValue(Boolean.FALSE));
assertEquals(ValueFactory.newInteger(111), mapper.toValue(111));
assertEquals(ValueFactory.newInteger(100500L), mapper.toValue(100500L));
assertEquals(ValueFactory.newFloat(111.0F), mapper.toValue(111.0F));
assertEquals(ValueFactory.newFloat(100.500D), mapper.toValue(100.500D));
assertEquals(ValueFactory.newString("hello"), mapper.toValue("hello"));
assertEquals(ValueFactory.newBinary(new byte[]{1, 2, 3, 4}), mapper.toValue(new byte[]{1, 2, 3, 4}));
// check default Value converters
assertEquals(Boolean.TRUE, mapper.fromValue(ValueFactory.newBoolean(true)));
assertEquals(Integer.valueOf(111), mapper.fromValue(ValueFactory.newInteger(111)));
assertEquals(Long.valueOf(4_000_000_000_000L), mapper.fromValue(ValueFactory.newInteger(4_000_000_000_000L)));
assertThrows(ClassCastException.class, () -> {
Long result = mapper.fromValue(ValueFactory.newInteger(111L));
});
assertEquals(Long.valueOf(111L), mapper.fromValue(ValueFactory.newInteger(111), Long.class));
assertEquals(Integer.valueOf(111), mapper.fromValue(ValueFactory.newInteger(111L), Integer.class));
assertEquals(111.0, mapper.fromValue(ValueFactory.newFloat(111.0F)));
assertEquals(Integer.valueOf(1000), mapper.fromValue(ValueFactory.newFloat(1000f), Integer.class));
assertEquals(Integer.valueOf(1000), mapper.fromValue(ValueFactory.newFloat(1000d), Integer.class));
assertEquals(Double.valueOf(Float.MAX_VALUE * 10D),
mapper.fromValue(ValueFactory.newFloat(Float.MAX_VALUE * 10D)));
assertEquals(Double.valueOf(111.0D), mapper.fromValue(ValueFactory.newFloat(111.0F), Double.class));
assertEquals(Double.valueOf(111.0D), mapper.fromValue(ValueFactory.newInteger(111L), Double.class));
assertEquals(Float.valueOf(111.0F), mapper.fromValue(ValueFactory.newFloat(111.0D), Float.class));
assertEquals(Float.valueOf(111.0F), mapper.fromValue(ValueFactory.newInteger(111), Float.class));
assertThrows(ClassCastException.class, () -> {
Float result = mapper.fromValue(ValueFactory.newInteger(4_000_000_000_000L));
});
assertEquals("hello", mapper.fromValue(ValueFactory.newString("hello")));
assertArrayEquals(new byte[]{1, 2, 3, 4}, mapper.fromValue(ValueFactory.newBinary(new byte[]{1, 2, 3, 4})));
// decimal
assertEquals(BigDecimal.ONE, mapper.fromValue(mapper.toValue(BigDecimal.ONE)));
// uuid
UUID uuid = UUID.fromString("84b56906-aeed-11ea-b3de-0242ac130004");
assertEquals(uuid, mapper.fromValue(mapper.toValue(uuid)));
// null
assertEquals(ValueFactory.newNil(), mapper.toValue(null));

Also, you can get the default mapper stack from TarantoolClient, change it, or use it:

MessagePackMapper mapper = client.getConfig().getMessagePackMapper();
client.callForSingleResult(
"get_array_as_single_result",
Collections.singletonList(Arrays.asList(1, 2, 3)),
v -> mapper.fromValue(v.asArrayValue(), (Class<List<Integer>>) (Class<?>) List.class)
)
.thenAccept(result -> assertEquals(3, result.size()))
.get();
Note: be careful changing the client mapper, it's used as the default mapper for API(call, space(spaceName).select, ...)

Choosing the target type

If you want to manually choose the target type for a particular field in a tuple, use get{targetTypeName} methods like getInteger, getString and others. See the entire list in TarantoolTuple interface

Conditions conditions = Conditions.any();
TarantoolResult<TarantoolTuple> selectResult = testSpace.select(conditions).get();
int countBeforeInsert = selectResult.size();
assertTrue(countBeforeInsert >= 2);
TarantoolTuple tuple = selectResult.get(0);
assertEquals(1, tuple.getInteger(0));
assertEquals("a1", tuple.getString(1));
assertEquals("Don Quixote", tuple.getString(2));
assertEquals("Miguel de Cervantes", tuple.getString(3));
assertEquals(1605, tuple.getInteger(4));

Creating your own converter

To create your own converter from Java basic types to MessagePack type you can implement ValueConverter interface and override fromValue, canConvertValue methods. Let's look at the converter implementation. This is default FloatValue to Float converter (FloatValue is a MessagePack type):

public class DefaultFloatValueToFloatConverter implements ValueConverter<FloatValue, Float> {
private static final long serialVersionUID = 20220418L;
@Override
public Float fromValue(FloatValue value) {
return value.toFloat();
}
@Override
public boolean canConvertValue(FloatValue value) {
double aDouble = value.toDouble();
return aDouble <= 0.0D ? isInAcceptableRange(0.0D - aDouble) : isInAcceptableRange(aDouble);
}
private boolean isInAcceptableRange(double aDouble) {
return (MIN_VALUE <= aDouble && aDouble <= MAX_VALUE) || aDouble == 0;
}
}

If you need converter which will convert MessagePack type to Java basic type (int, double, String and etc.) you can implement ObjectConverter interface and override toValue, canConvertObject methods:

public class DefaultFloatToFloatValueConverter implements ObjectConverter<Float, FloatValue> {
private static final long serialVersionUID = 20220418L;
@Override
public FloatValue toValue(Float object) {
return ValueFactory.newFloat(object);
}
}

Default canConvertObject and canConvertValue implementation always returns true. If any value of source type can be converted to target type you do not have to implement these methods.

Adding converter to mapper

To add MessagePack type converter to the mapper use method registerValueConverter:

DefaultMessagePackMapper mapper = DefaultMessagePackMapperFactory.getInstance().defaultSimpleTypeMapper();
Map<Value, Value> testValue = new HashMap<>();
CustomTuple testTuple = new CustomTuple(1234, "Test");
testValue.put(ValueFactory.newString("id"), ValueFactory.newInteger(testTuple.getId()));
testValue.put(ValueFactory.newString("name"), ValueFactory.newString(testTuple.getName()));
assertThrows(MessagePackValueMapperException.class, () -> mapper.fromValue(ValueFactory.newMap(testValue)));
mapper.registerValueConverter(ValueType.MAP, CustomTuple.class,
(ValueConverter<MapValue, CustomTuple>) v -> {
CustomTuple tuple = new CustomTuple();
Map<Value, Value> keyValue = v.map();
tuple.setId(keyValue.get(ValueFactory.newString("id")).asIntegerValue().asInt());
tuple.setName(keyValue.get(ValueFactory.newString("name")).asStringValue().asString());
return tuple;
});
assertEquals(testTuple, mapper.fromValue(ValueFactory.newMap(testValue)));

To add Java type converter use method registerObjectConverter:

DefaultMessagePackMapper mapper = DefaultMessagePackMapperFactory.getInstance().defaultSimpleTypeMapper();
CustomTuple testTuple = new CustomTuple(1234, "Test");
assertThrows(MessagePackObjectMapperException.class, () -> mapper.toValue(testTuple));
mapper.registerObjectConverter(CustomTuple.class, MapValue.class, t -> {
Map<Value, Value> keyValue = new HashMap<>();
keyValue.put(ValueFactory.newString("id"), ValueFactory.newInteger(t.getId()));
keyValue.put(ValueFactory.newString("name"), ValueFactory.newString(t.getName()));
return ValueFactory.newMap(keyValue);
});
Map<Value, Value> testValue = new HashMap<>();
testValue.put(ValueFactory.newString("id"), ValueFactory.newInteger(testTuple.getId()));
testValue.put(ValueFactory.newString("name"), ValueFactory.newString(testTuple.getName()));
assertEquals(testValue, mapper.toValue(testTuple).asMapValue().map());

After adding converter to the mapper this converter will have maximum priority because it will be added to the top of the mapper stack. Converters for container types (like lists and maps) can use mappers inside too, such converters (and mappers holding them) will be in fact recursive.

Creating your own mapper

If you need to work with a custom structure of the received MessagePack objects, e.g. from a custom Lua method, use the mapper factory:

MessagePackValueMapper valueMapper = client.getConfig().getMessagePackMapper();
CallResultMapper<TestComposite, SingleValueCallResult<TestComposite>> mapper =
client.getResultMapperFactoryFactory().<TestComposite>singleValueResultMapperFactory()
.withSingleValueResultConverter(v -> {
Map<String, Object> valueMap = valueMapper.fromValue(v);
TestComposite composite = new TestComposite();
composite.field1 = (String) valueMap.get("field1");
composite.field2 = (Integer) valueMap.get("field2");
composite.field3 = (Boolean) valueMap.get("field3");
composite.field4 = (Double) valueMap.get("field4");
return composite;
}, TestCompositeCallResult.class);
TestComposite actual =
client.callForSingleResult("get_composite_data", Collections.singletonList(123000), mapper).get();
assertEquals("Jane Doe", actual.field1);
assertEquals(999, actual.field2);
assertEquals(true, actual.field3);
assertEquals(123.456, actual.field4);