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

Fix JsonNumber bson serialization #898

Open
wants to merge 1 commit into
base: 2.11.x
Choose a base branch
from
Open
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
Expand Up @@ -279,6 +279,11 @@ protected Number getBestNumber() {
};
}

@Override
protected BigDecimal getBigDecimalFromNumber(Number number) {
return ((Decimal128) number).bigDecimalValue();
Copy link
Contributor

Choose a reason for hiding this comment

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

This can be implemented using instanceOf or different convertions

}

@Override
public byte @NonNull [] decodeBinary() throws IOException {
if (currentBsonType == BsonType.BINARY) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,6 @@ public void encodeDouble(double value) {
@Override
public void encodeBigInteger(BigInteger value) {
encodeBigDecimal(new BigDecimal(value));
postEncodeValue();
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package io.micronaut.serde.bson

import io.micronaut.core.type.Argument
import io.micronaut.json.JsonMapper
import io.micronaut.json.tree.JsonNode
import io.micronaut.serde.AbstractBasicSerdeSpec
import io.micronaut.test.extensions.spock.annotation.MicronautTest
import jakarta.inject.Inject
Expand Down Expand Up @@ -46,4 +47,23 @@ class BsonBinaryBasicSerdeSpec extends AbstractBasicSerdeSpec implements BsonBin
assert result == expected
return result == expected
}

def "validate json node including type"() {
when:
def result = serializeDeserializeAs(
JsonNode.createObjectNode(["v": jsonNode]), Argument.of(JsonNode.class)).get("v")
then:
result.value == jsonNode.value && result.value.class == jsonNode.value.class

where:
// the type doesn't match for float and big integer as bson encodes them as double and big decimal
jsonNode << [
JsonNode.createBooleanNode(true),
JsonNode.createNumberNode(123),
JsonNode.createNumberNode(234L),
JsonNode.createNumberNode(123.234D),
JsonNode.createNumberNode(BigDecimal.valueOf(12345.12345)),
JsonNode.createStringNode("Hello"),
]
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -402,6 +402,19 @@ protected int getInteger() throws IOException {
*/
protected abstract Number getBestNumber() throws IOException;

/**
* Converts the number, probably retrieved by calling {@link #getBestNumber()}, to a {@link BigDecimal},
* when it is not one of {@link Byte}, {@link Short}, {@link Integer}, {@link Long}, {@link Float},
* {@link Double}, {@link BigInteger}, {@link BigDecimal}
*
* @param number The number value
* @return The number as a big decimal
* @throws UnsupportedOperationException If custom number types are not expected
*/
protected BigDecimal getBigDecimalFromNumber(Number number) {
throw new UnsupportedOperationException();
Copy link
Member

Choose a reason for hiding this comment

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

Definitely needs a proper default implementation.

}

/**
* Decode the current {@link TokenType#NUMBER} value as a numeric {@link JsonNode}. Called for no other token type.
* Default implementation tries to construct a node from {@link #getBestNumber()}.
Expand All @@ -424,7 +437,7 @@ protected JsonNode getBestNumberNode() throws IOException {
return JsonNode.createNumberNode((BigDecimal) number);
} else {
// fallback, unknown number type
return JsonNode.createNumberNode(getBigDecimal());
return JsonNode.createNumberNode(getBigDecimalFromNumber(number));
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,14 @@ private void serialize0(Encoder encoder, JsonNode value) throws IOException {
encoder.encodeString(value.getStringValue());
} else if (value.isNumber()) {
Number numberValue = value.getNumberValue();
if (numberValue instanceof Integer || numberValue instanceof Byte || numberValue instanceof Short || numberValue instanceof Long) {
if (numberValue instanceof Integer) {
encoder.encodeInt(numberValue.intValue());
} else if (numberValue instanceof Long) {
encoder.encodeLong(numberValue.longValue());
} else if (numberValue instanceof Short) {
encoder.encodeShort(numberValue.shortValue());
} else if (numberValue instanceof Byte) {
encoder.encodeByte(numberValue.byteValue());
} else if (numberValue instanceof BigInteger bi) {
encoder.encodeBigInteger(bi);
} else if (numberValue instanceof BigDecimal bd) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ package io.micronaut.serde

import io.micronaut.core.type.Argument
import io.micronaut.json.JsonMapper
import io.micronaut.json.tree.JsonNode
import io.micronaut.serde.config.annotation.SerdeConfig
import io.micronaut.serde.data.Users1
import io.micronaut.serde.data.Users2
Expand Down Expand Up @@ -221,6 +222,27 @@ abstract class AbstractBasicSerdeSpec extends Specification implements JsonSpec,
result.bigInteger == BigInteger.valueOf(123456789)
}

def "validate json node"() {
when:
def result = serializeDeserializeAs(
JsonNode.createObjectNode(["v": jsonNode]), Argument.of(JsonNode.class)).get("v")
then:
// note that the type of the result may differ from the original as json
// doesn't keep this information
result.value == jsonNode.value
where:
jsonNode << [
JsonNode.createBooleanNode(true),
JsonNode.createNumberNode(123),
JsonNode.createNumberNode(234L),
JsonNode.createNumberNode(11.22f),
JsonNode.createNumberNode(123.234D),
JsonNode.createNumberNode(BigInteger.valueOf(123456789)),
JsonNode.createNumberNode(BigDecimal.valueOf(12345.12345)),
JsonNode.createStringNode("Hello"),
]
}

def "should skip unknown values"() {
when:
def all = jsonMapper.readValue(jsonAsBytes("""{"unknown":"ABC"}"""), Argument.of(AllTypesBean))
Expand Down Expand Up @@ -309,8 +331,12 @@ abstract class AbstractBasicSerdeSpec extends Specification implements JsonSpec,
}

def <T> T serializeDeserialize(T obj) {
return serializeDeserializeAs(obj, Argument.of(obj.getClass()))
}

def <T> T serializeDeserializeAs(T obj, Argument type) {
def output = jsonMapper.writeValueAsBytes(obj)
return jsonMapper.readValue(output, Argument.of(obj.getClass())) as T
return jsonMapper.readValue(output, type) as T
}

byte[] jsonAsBytes(String json) {
Expand Down
Loading