Java to Jackson Json Serialization: Money Fields

JSON Deserialization Joda Money using Jackson ObjectMapper causes exception

Here is an example that register serializer and deserializer for the Joda Money type. All the Money objects are converted to a JSON string.

public class JacksonJodaMoney {

public static class Product {
public final Money price;

@JsonCreator
public Product(@JsonProperty("price") Money price) {
this.price = price;
}

@Override
public String toString() {
return "Product{" +
"price=" + price +
'}';
}
}

private static class MoneySerializer extends StdSerializer<Money> {
protected MoneySerializer() {
super(Money.class);
}

@Override
public void serialize(Money value, JsonGenerator jgen, SerializerProvider provider) throws IOException {
jgen.writeString(value.toString());
}
}

private static class MoneyDeserializer extends StdDeserializer<Money> {
protected MoneyDeserializer() {
super(Money.class);
}

@Override
public Money deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException {
return Money.parse(jp.readValueAs(String.class));
}
}

public static void main(String[] args) throws IOException {
ObjectMapper mapper = new ObjectMapper();
Product value = new Product(Money.of(CurrencyUnit.EUR, 40.55));
SimpleModule module = new SimpleModule();
module.addDeserializer(Money.class, new MoneyDeserializer());
module.addSerializer(Money.class, new MoneySerializer());
mapper.registerModule(module);
String json = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(value);
System.out.println(json);
System.out.println(mapper.readValue(json, Product.class));
}
}

Output:

{
"price" : "EUR 40.55"
}
Product{price=EUR 40.55}

Jackson JSON Serialization without field name

From the wiki page it sounds like the @JsonUnwrapped annotation should do what you want.

@JsonUnwrapped: property annotation used to define that value should be "unwrapped" when serialized (and wrapped again when deserializing), resulting in flattening of data structure, compared to POJO structure.

The Javadoc for the class also has an example that looks appropriate.

Jackson JSON custom serialization for certain fields

You can implement a custom serializer as follows:

public class Person {
public String name;
public int age;
@JsonSerialize(using = IntToStringSerializer.class, as=String.class)
public int favoriteNumber:
}


public class IntToStringSerializer extends JsonSerializer<Integer> {

@Override
public void serialize(Integer tmpInt,
JsonGenerator jsonGenerator,
SerializerProvider serializerProvider)
throws IOException, JsonProcessingException {
jsonGenerator.writeObject(tmpInt.toString());
}
}

Java should handle the autoboxing from int to Integer for you.

Does Jackson serialize getter property for an undefined field?

Jackson by default uses the getters for serializing and setters for deserializing.

You can use @JsonIgnore over your getter method to ignore it, OR you can configure your object mapper to use the fields only for serialization/des:

ObjectMapper objectMapper = new ObjectMapper();

objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.NONE);
objectMapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY);

Jackson: Serialize only marked fields

There seems to be a way to configure ObjectMapper to ignore all non annotated fields.

ObjectMapper mapper = new ObjectMapper();
mapper.setVisibilityChecker(getSerializationConfig().getDefaultVisibilityChecker()
.withCreatorVisibility(JsonAutoDetect.Visibility.NONE)
.withFieldVisibility(JsonAutoDetect.Visibility.NONE)
.withGetterVisibility(JsonAutoDetect.Visibility.NONE)
.withIsGetterVisibility(JsonAutoDetect.Visibility.NONE)
.withSetterVisibility(JsonAutoDetect.Visibility.NONE));

Source

Deserialize multiple fields to one by Jackson

This is not possible with your current setup, you provide to the deserializer only the val node, but you need the entire object to access scale node.

Since using @JsonCreator is undesirable, you could change the deserializer to handle ValueClass:

public class ValueDeserializer extends StdDeserializer<ValueClass> {

public ValueDeserializer() {
super(ValueClass.class);
}

@Override
public ValueClass deserialize(JsonParser parser, DeserializationContext context) throws IOException {
JsonNode node = parser.getCodec().readTree(parser);
int scale = node.get("scale").asInt();
ValueClass valueClass = new ValueClass();

JavaType javaType = context.getTypeFactory().constructType(ValueClass.class);
// Introspect the given type
BeanDescription beanDescription = context.getConfig().introspect(javaType);
// Find properties
List<BeanPropertyDefinition> properties = beanDescription.findProperties();
for (BeanPropertyDefinition property : properties) {
String propertyName = property.getName();//get name as in json
String propertyValue = node.get(propertyName).asText();
BigDecimal decimal = new BigDecimal(propertyValue).scaleByPowerOfTen(-scale);
AnnotatedMember accessor = property.getMutator();
accessor.setValue(valueClass, decimal);
}
return valueClass;
}
}

To avoid manually writing property names and setting their values, properties are introspected from java type. This approach is heavily inspired by this answer, you can check it for additional info and possible pitfalls. I believe setting the rest of the fields should be straightforward, using this as a basis.

And simple test:

@JsonDeserialize(using = ValueDeserializer.class)
public class ValueClass {

@JsonProperty("val1")
private BigDecimal value1;
private BigDecimal val2;
private BigDecimal val3;

//setters and getters

@Override
public String toString() {
return "ValueClass{" +
"value1=" + value1 +
", val2=" + val2 +
", val3=" + val3 +
'}';
}
}

Main:

public class Main {

public static void main(String[] args) throws Exception {
String json = "{\"val1\":501, \"scale\":2, \"val2\":407, \"val3\":86}";
ObjectMapper mapper = new ObjectMapper();
ValueClass value = mapper.readValue(json, ValueClass.class);
System.out.println(value);
}
}

Prints - ValueClass{value1=5.01, val2=4.07, val3=0.86}.

Customizing Databind Json serialization format of MonetaryAmount JSR354 / moneta

JsonFormat is an annotation used in several (de)serializers defined by Jackson (e.g. DateTimeSerializerBase, NumberSerializers.Base and some other, full list here), it's not a general purpose mechanism turning any object into a string:

Unlike most other Jackson annotations, annotation does not have
specific universal interpretation: instead, effect depends on datatype
of property being annotated (or more specifically, deserializer and
serializer being used).

Specifying it won't have any effect unless you create a custom serializer for MonetaryAmount or use one that makes use of this annotation (and also its pattern property), but if you create a custom serializer, chances are you won't need that level of flexibility as to specify different patterns for different fields and could just use a fixed MonetaryAmountFormat or build the necessary string from the MonetaryAmount object otherwise.

For example

@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonPropertyOrder({
"CBNOT_CHARGEBACK_AMOUNT"
})
public class TestMonetaryAmountJsonSerialization {
@JsonProperty("CBNOT_CHARGEBACK_AMOUNT")
@NotNull
private final MonetaryAmount chargebackAmount = Monetary.getDefaultAmountFactory().setCurrency("USD").setNumber(12.50).create();

private static final ObjectMapper mapper = new ObjectMapper();
static {
SimpleModule monetaryModule = new SimpleModule();
monetaryModule.addSerializer(MonetaryAmount.class, new MonetaryAmountSerializer());
mapper.registerModule(monetaryModule);
}

@Test
public void testThis() throws JsonProcessingException {
String json = mapper.writeValueAsString(this);
System.out.println(json);
Assert.assertEquals("{\"CBNOT_CHARGEBACK_AMOUNT\":\"$12.50\"}", json);
}

public static class MonetaryAmountSerializer extends JsonSerializer<MonetaryAmount> {
public void serialize(MonetaryAmount monetaryAmount,
JsonGenerator jsonGenerator,
SerializerProvider serializerProvider) throws IOException {
StringBuilder sb = new StringBuilder();
MonetaryAmountDecimalFormatBuilder
.of("¤#,##0.00").withCurrencyUnit(monetaryAmount.getCurrency()).build()
.print(sb, monetaryAmount);
jsonGenerator.writeString(sb.toString());
}
}
}

Jackson How to add additional properties during searlization without making changes to default POJO?

You can try like below in the controller level, (it would be better to manipulate the response by using the Interceptor or Filter)

   @GetMapping
public ObjectNode test() throws JsonProcessingException {

CustomClass customClass = new CustomClass();
customClass.setAge(30);
customClass.setName("Batman");

ObjectMapper mapper = new ObjectMapper();
String jsonStr = mapper.writeValueAsString(customClass);
ObjectNode nodes = mapper.readValue(jsonStr, ObjectNode.class);
nodes.put("job", "HR ");
nodes.put("company", "New ");

return nodes;
}

Response:

{
name: "Batman",
age: 30,
job: "HR ",
company: "New "
}

Your new test driver is below,

 public static void main(String[] args) throws JsonProcessingException {
CustomClass customClass = new CustomClass();
customClass.setAge(30);
customClass.setName("Batman");

ObjectMapper mapper = new ObjectMapper();
String jsonStr = mapper.writeValueAsString(customClass);
ObjectNode nodes = mapper.readValue(jsonStr, ObjectNode.class);
nodes.put("job", "HR ");
nodes.put("company", "New ");
System.out.println(nodes);

}

Output: {"name":"Batman","age":30,"job":"HR ","company":"New "}


Updated

but I get the error: Type id handling not implemented for type
package.ClassName (by serializer of type
package.CustomModule$CustomClassSerializer)

Write new object fields "job" and "company" to your custom class serializer.

public class Test {
public static void main(String args[]) throws Exception {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(new CustomModule());
CustomClass cc = new CustomClass();
cc.setAge(30);
cc.setName("Batman");
StringWriter sw = new StringWriter();
objectMapper.writeValue(sw, cc);
System.out.println(sw.toString());
}

public static class CustomModule extends SimpleModule {
public CustomModule() {
addSerializer(CustomClass.class, new CustomClassSerializer());
}

private static class CustomClassSerializer extends JsonSerializer {
@Override
public void serialize(Object value, JsonGenerator jgen, SerializerProvider provider) throws IOException {
// Validate.isInstanceOf(CustomClass.class, value);
jgen.writeStartObject();
JavaType javaType = provider.constructType(CustomClass.class);
BeanDescription beanDesc = provider.getConfig().introspect(javaType);
JsonSerializer<Object> serializer = BeanSerializerFactory.instance.findBeanSerializer(provider,
javaType, beanDesc);
// this is basically your 'writeAllFields()'-method:
serializer.unwrappingSerializer(null).serialize(value, jgen, provider);
jgen.writeObjectField("job", "HR ");
jgen.writeObjectField("company", "New ");
jgen.writeEndObject();
}
}
}
}

Output: {"name":"Batman","age":30,"job":"HR ","company":"New "}



Related Topics



Leave a reply



Submit