yasson should support (de)serializing of "java.math.MathContext"
Describe the bug
I get this error when trying to create json output of an instance of java.math.MathContext.
Caused by: java.lang.reflect.InaccessibleObjectException: Unable to make field final java.math.RoundingMode java.math.MathContext.roundingMode accessible: module java.base does not "opens java.math" to unnamed module @4dca0ecd
at java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:354) ~[?:?]
at java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:297) ~[?:?]
at java.lang.reflect.Field.checkCanSetAccessible(Field.java:178) ~[?:?]
at java.lang.reflect.Field.setAccessible(Field.java:172) ~[?:?]
at org.eclipse.yasson.internal.model.PropertyValuePropagation.lambda$overrideAccessible$2(PropertyValuePropagation.java:153) ~[yasson-1.0.9.jar:?]
at java.security.AccessController.doPrivileged(AccessController.java:318) ~[?:?]
at org.eclipse.yasson.internal.model.PropertyValuePropagation.overrideAccessible(PropertyValuePropagation.java:152) ~[yasson-1.0.9.jar:?]
at org.eclipse.yasson.internal.model.PropertyValuePropagation.isFieldVisible(PropertyValuePropagation.java:127) ~[yasson-1.0.9.jar:?]
at org.eclipse.yasson.internal.model.PropertyValuePropagation.initReadable(PropertyValuePropagation.java:94) ~[yasson-1.0.9.jar:?]
at org.eclipse.yasson.internal.model.PropertyValuePropagation.<init>(PropertyValuePropagation.java:80) ~[yasson-1.0.9.jar:?]
at org.eclipse.yasson.internal.model.ReflectionPropagation.<init>(ReflectionPropagation.java:39) ~[yasson-1.0.9.jar:?]
at org.eclipse.yasson.internal.model.PropertyModel.<init>(PropertyModel.java:134) ~[yasson-1.0.9.jar:?]
at org.eclipse.yasson.internal.ClassParser.lambda$parseProperties$0(ClassParser.java:71) ~[yasson-1.0.9.jar:?]
at java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:197) ~[?:?]
at java.util.HashMap$ValueSpliterator.forEachRemaining(HashMap.java:1779) ~[?:?]
at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:509) ~[?:?]
at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:499) ~[?:?]
at java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:921) ~[?:?]
at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234) ~[?:?]
at java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:682) ~[?:?]
at org.eclipse.yasson.internal.ClassParser.parseProperties(ClassParser.java:72) ~[yasson-1.0.9.jar:?]
at org.eclipse.yasson.internal.MappingContext.lambda$createParseClassModelFunction$1(MappingContext.java:97) ~[yasson-1.0.9.jar:?]
at java.util.concurrent.ConcurrentHashMap.computeIfAbsent(ConcurrentHashMap.java:1708) ~[?:?]
at org.eclipse.yasson.internal.MappingContext.getOrCreateClassModel(MappingContext.java:81) ~[yasson-1.0.9.jar:?]
at org.eclipse.yasson.internal.serializer.ObjectSerializer.serializeInternal(ObjectSerializer.java:65) ~[yasson-1.0.9.jar:?]
at org.eclipse.yasson.internal.serializer.AbstractContainerSerializer.serialize(AbstractContainerSerializer.java:107) ~[yasson-1.0.9.jar:?]
at org.eclipse.yasson.internal.serializer.AbstractContainerSerializer.serializerCaptor(AbstractContainerSerializer.java:125) ~[yasson-1.0.9.jar:?]
at org.eclipse.yasson.internal.serializer.ObjectSerializer.marshallProperty(ObjectSerializer.java:121) ~[yasson-1.0.9.jar:?]
at org.eclipse.yasson.internal.serializer.ObjectSerializer.serializeInternal(ObjectSerializer.java:69) ~[yasson-1.0.9.jar:?]
at org.eclipse.yasson.internal.serializer.AbstractContainerSerializer.serialize(AbstractContainerSerializer.java:107) ~[yasson-1.0.9.jar:?]
at org.eclipse.yasson.internal.serializer.AbstractContainerSerializer.serializerCaptor(AbstractContainerSerializer.java:125) ~[yasson-1.0.9.jar:?]
at org.eclipse.yasson.internal.serializer.ObjectSerializer.marshallProperty(ObjectSerializer.java:121) ~[yasson-1.0.9.jar:?]
at org.eclipse.yasson.internal.serializer.ObjectSerializer.serializeInternal(ObjectSerializer.java:69) ~[yasson-1.0.9.jar:?]
at org.eclipse.yasson.internal.serializer.AbstractContainerSerializer.serialize(AbstractContainerSerializer.java:107) ~[yasson-1.0.9.jar:?]
at org.eclipse.yasson.internal.serializer.AbstractContainerSerializer.serializerCaptor(AbstractContainerSerializer.java:125) ~[yasson-1.0.9.jar:?]
at org.eclipse.yasson.internal.serializer.AbstractContainerSerializer.serializeItem(AbstractContainerSerializer.java:194) ~[yasson-1.0.9.jar:?]
at org.eclipse.yasson.internal.serializer.MapToObjectSerializer.serializeContainer(MapToObjectSerializer.java:99) ~[yasson-1.0.9.jar:?]
at org.eclipse.yasson.internal.serializer.MapSerializer.serializeInternal(MapSerializer.java:156) ~[yasson-1.0.9.jar:?]
at org.eclipse.yasson.internal.serializer.MapSerializer.serializeInternal(MapSerializer.java:27) ~[yasson-1.0.9.jar:?]
at org.eclipse.yasson.internal.serializer.AbstractContainerSerializer.serialize(AbstractContainerSerializer.java:107) ~[yasson-1.0.9.jar:?]
at org.eclipse.yasson.internal.serializer.AbstractContainerSerializer.serializerCaptor(AbstractContainerSerializer.java:125) ~[yasson-1.0.9.jar:?]
at org.eclipse.yasson.internal.serializer.ObjectSerializer.marshallProperty(ObjectSerializer.java:121) ~[yasson-1.0.9.jar:?]
at org.eclipse.yasson.internal.serializer.ObjectSerializer.serializeInternal(ObjectSerializer.java:69) ~[yasson-1.0.9.jar:?]
... 79 more
To Reproduce Steps to reproduce the bug
Expected behavior
To be able to (de)serialize java.math.MathContext.
System information:
- OS: Mac
- Java Version: 17
- Yasson Version: 1.0.9
Could you provide a reproducer please?.
@jbescos Here is the test case leading to this exception:
import static org.junit.jupiter.api.Assertions.assertNotNull;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.math.MathContext;
import java.math.RoundingMode;
import javax.json.bind.JsonbBuilder;
import javax.json.bind.JsonbConfig;
import javax.json.bind.config.PropertyVisibilityStrategy;
import org.junit.jupiter.api.Test;
class BigDecimalJsonTest {
public static class NumberText {
private final BigDecimal number;
private final MathContext mathContext;
public NumberText(BigDecimal number, MathContext mathContext) {
this.number = number;
this.mathContext = mathContext;
}
public BigDecimal getNumber() {
return number;
}
public MathContext getMathContext() {
return mathContext;
}
}
public static final class PrivateVisibilityStrategy implements PropertyVisibilityStrategy {
@Override
public boolean isVisible(Field field) {
return true;
}
@Override
public boolean isVisible(Method method) {
return method.isAnnotationPresent(javax.json.bind.annotation.JsonbProperty.class);
}
}
@Test
final void testNumberText() {
var mathContext = new MathContext(32, RoundingMode.HALF_UP);
var numberText = new NumberText(BigDecimal.valueOf(5.3), mathContext);
var json = toJson(numberText);
System.out.println(json);
assertNotNull(json);
}
public static String toJson(Object object) {
try (var jsonb = JsonbBuilder.create(new JsonbConfig().withFormatting(true)
.withNullValues(true)
// using "PrivateVisibilityStrategy" leads to an exception
.withPropertyVisibilityStrategy(new PrivateVisibilityStrategy()))) {
return jsonb.toJson(object);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}
I found the reason for this exception:
I am using the PrivateVisibilityStrategy which leads to this exception. However, it's not very convenient to turn this off because in my case NumberText is just a property of another class hierarchy where I need the PrivateVisibilityStrategy. So if I disable it just because of MathContext, then the PrivateVisibilityStrategy for all other properties within the class hierarchy will also be disabled.
I think the issue is that java.math module is not opened to java.base module, but you can explicitly open it with this JVM argument:
--add-opens java.base/java.math=ALL-UNNAMED
MathContext is not supported by Yasson currently. If you need this to be serialized/deserialized properly, please create your own custom Serializer/Deserializer for it.
@Verdent Look here with the current version:
Caused by: jakarta.json.bind.JsonbException: Unable to serialize property 'amount' from user.Item
at deployment.test.war/org.eclipse.yasson.internal.serializer.ObjectSerializer.lambda$serialize$0(ObjectSerializer.java:43)
at java.base/java.util.LinkedHashMap.forEach(LinkedHashMap.java:721)
at deployment.test.war/org.eclipse.yasson.internal.serializer.ObjectSerializer.serialize(ObjectSerializer.java:38)
at deployment.test.war/org.eclipse.yasson.internal.serializer.RecursionChecker.serialize(RecursionChecker.java:38)
at deployment.test.war/org.eclipse.yasson.internal.serializer.KeyWriter.serialize(KeyWriter.java:41)
at deployment.test.war/org.eclipse.yasson.internal.serializer.NullVisibilitySwitcher.serialize(NullVisibilitySwitcher.java:40)
at deployment.test.war/org.eclipse.yasson.internal.serializer.NullSerializer.serialize(NullSerializer.java:67)
at deployment.test.war/org.eclipse.yasson.internal.serializer.types.ObjectTypeSerializer.findSerializer(ObjectTypeSerializer.java:68)
at deployment.test.war/org.eclipse.yasson.internal.serializer.types.ObjectTypeSerializer.serializeValue(ObjectTypeSerializer.java:50)
at deployment.test.war/org.eclipse.yasson.internal.serializer.types.TypeSerializer$ValueSerializer.serialize(TypeSerializer.java:51)
at deployment.test.war/org.eclipse.yasson.internal.serializer.types.TypeSerializer.serialize(TypeSerializer.java:37)
at deployment.test.war/org.eclipse.yasson.internal.serializer.types.ObjectTypeSerializer.serialize(ObjectTypeSerializer.java:31)
at deployment.test.war/org.eclipse.yasson.internal.serializer.NullSerializer.serialize(NullSerializer.java:67)
at deployment.test.war/org.eclipse.yasson.internal.serializer.CollectionSerializer.lambda$serialize$0(CollectionSerializer.java:37)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
at deployment.test.war/org.eclipse.yasson.internal.serializer.CollectionSerializer.serialize(CollectionSerializer.java:37)
at deployment.test.war/org.eclipse.yasson.internal.serializer.KeyWriter.serialize(KeyWriter.java:41)
at deployment.test.war/org.eclipse.yasson.internal.serializer.NullVisibilitySwitcher.serialize(NullVisibilitySwitcher.java:40)
at deployment.test.war/org.eclipse.yasson.internal.serializer.NullSerializer.serialize(NullSerializer.java:67)
at deployment.test.war/org.eclipse.yasson.internal.serializer.ValueGetterSerializer.serialize(ValueGetterSerializer.java:43)
at deployment.test.war/org.eclipse.yasson.internal.serializer.ObjectSerializer.lambda$serialize$0(ObjectSerializer.java:41)
... 104 more
Caused by: java.lang.reflect.InaccessibleObjectException: Unable to make field final java.math.RoundingMode java.math.MathContext.roundingMode accessible: module java.base does not "opens java.math" to unnamed module @765877f0
at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:354)
at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:297)
at java.base/java.lang.reflect.Field.checkCanSetAccessible(Field.java:180)
at java.base/java.lang.reflect.Field.setAccessible(Field.java:174)
at deployment.test.war/org.eclipse.yasson.internal.model.PropertyModel.lambda$overrideAccessible$2(PropertyModel.java:594)
at java.base/java.security.AccessController.doPrivileged(AccessController.java:318)
at deployment.test.war/org.eclipse.yasson.internal.model.PropertyModel.overrideAccessible(PropertyModel.java:593)
at deployment.test.war/org.eclipse.yasson.internal.model.PropertyModel.isFieldVisible(PropertyModel.java:568)
at deployment.test.war/org.eclipse.yasson.internal.model.PropertyModel.createReadHandle(PropertyModel.java:516)
at deployment.test.war/org.eclipse.yasson.internal.model.PropertyModel.<init>(PropertyModel.java:157)
at deployment.test.war/org.eclipse.yasson.internal.ClassParser.lambda$parseProperties$0(ClassParser.java:70)
at java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:197)
at java.base/java.util.HashMap$ValueSpliterator.forEachRemaining(HashMap.java:1779)
at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:509)
at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:499)
at java.base/java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:921)
at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
at java.base/java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:682)
at deployment.test.war/org.eclipse.yasson.internal.ClassParser.parseProperties(ClassParser.java:71)
at deployment.test.war/org.eclipse.yasson.internal.MappingContext.lambda$createParseClassModelFunction$1(MappingContext.java:105)
at java.base/java.util.concurrent.ConcurrentHashMap.computeIfAbsent(ConcurrentHashMap.java:1708)
at deployment.test.war/org.eclipse.yasson.internal.MappingContext.getOrCreateClassModel(MappingContext.java:77)
at deployment.test.war/org.eclipse.yasson.internal.serializer.SerializationModelCreator.serializerChainInternal(SerializationModelCreator.java:187)
at deployment.test.war/org.eclipse.yasson.internal.serializer.SerializationModelCreator.serializerChain(SerializationModelCreator.java:137)
at deployment.test.war/org.eclipse.yasson.internal.serializer.SerializationModelCreator.memberSerializer(SerializationModelCreator.java:381)
at deployment.test.war/org.eclipse.yasson.internal.serializer.SerializationModelCreator.createObjectSerializer(SerializationModelCreator.java:213)
at deployment.test.war/org.eclipse.yasson.internal.serializer.SerializationModelCreator.serializerChainInternal(SerializationModelCreator.java:199)
at deployment.test.war/org.eclipse.yasson.internal.serializer.SerializationModelCreator.serializerChainRuntime(SerializationModelCreator.java:123)
at deployment.test.war/org.eclipse.yasson.internal.serializer.types.ObjectTypeSerializer.lambda$findSerializer$0(ObjectTypeSerializer.java:67)
at java.base/java.util.concurrent.ConcurrentHashMap.computeIfAbsent(ConcurrentHashMap.java:1708)
at deployment.test.war/org.eclipse.yasson.internal.serializer.types.ObjectTypeSerializer.findSerializer(ObjectTypeSerializer.java:65)
at deployment.test.war/org.eclipse.yasson.internal.serializer.types.ObjectTypeSerializer.serializeValue(ObjectTypeSerializer.java:50)
at deployment.test.war/org.eclipse.yasson.internal.serializer.types.TypeSerializer$ValueSerializer.serialize(TypeSerializer.java:51)
at deployment.test.war/org.eclipse.yasson.internal.serializer.types.TypeSerializer.serialize(TypeSerializer.java:37)
at deployment.test.war/org.eclipse.yasson.internal.serializer.types.ObjectTypeSerializer.serialize(ObjectTypeSerializer.java:31)
at deployment.test.war/org.eclipse.yasson.internal.serializer.NullSerializer.serialize(NullSerializer.java:67)
at deployment.test.war/org.eclipse.yasson.internal.serializer.ValueGetterSerializer.serialize(ValueGetterSerializer.java:43)
at deployment.test.war/org.eclipse.yasson.internal.serializer.ObjectSerializer.lambda$serialize$0(ObjectSerializer.java:41)
... 124 more
MathContext is not supported by Yasson currently.
Why? It has only two properties final int precision; and final RoundingMode roundingMode; (or their getters getPrecision(), getRoundingMode()) which can be easily (de)serialized by yasson. I think I can remember that this was already possible in one of the earlier versions of Yasson, because I used that lib back then and didn't have these problems with exactly this class. But I don't remember which version was suddenly no longer possible. I think, in combination with (de)serializing BigDecimal, de)serializing MathContext as well has it needs..
What must be done to support it?
I can provide a PR. I wrote a MathContextAdapter:
public class MathContextAdapter implements JsonbAdapter<MathContext, JsonObject> {
@Override
public JsonObject adaptToJson(MathContext mathContext) throws Exception {
return Json.createObjectBuilder()
.add("precision", mathContext.getPrecision())
.add("roundingMode", mathContext.getRoundingMode().name())
.build();
}
@Override
public MathContext adaptFromJson(JsonObject jsonObject) throws Exception {
var precision = jsonObject.getInt("precision");
var roundingMode = jsonObject.getString("roundingMode");
MathContext mathContext = new MathContext(precision, RoundingMode.valueOf(roundingMode));
return mathContext;
}
}
However, looking into e.g. https://github.com/eclipse-ee4j/yasson/tree/master/src/main/java/org/eclipse/yasson/internal, Yasson only accepts serializers and deserializers. Should I then implement MathContextSerializer/MathContextDeserializer?
I think it would be good to support MathContext by default (so the user does not need to write a adapter) since it is a Java built-in class and like other classes (like ZoneId, which is also supported by Yasson) it is a common Java class.
Another question: Why do I even have to register such a (De)serializer to be able to (de)serialize this class? Normally Json-B can (de)serialize a common class like this (consisting only of a String and an Enum property) by default.