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
How to Shut Down a Spring Boot Command-Line Application
Statefulbeantocsv With Column Headers
Parsing the Nested Json Array Using Jackson Library in Java
Cannot Get Jedis Connection; Could Not Get a Resource from the Pool
Password Encrypt and Decrypt Using Spring-Security
Java Cut String After 2 Decimal Digits
Java.Sql.Sqlexception: Field Doesn't Have a Default Value
How to Count Method Calls by Instance
How to Subtract Number of Days from Current Date in Hql Query
Simplifying the If-Else Condition Using Java Functional Style Programming
Use a Binary Search on an Int Array Sorted in Descending Order
Can a @Manytoone JPA Relation Be Null
How to Get My Loops to Display Horizontally Instead of Vertically
How to Accept Only Numeric Values in a Jtextfield
Hibernate Error - Querysyntaxexception: Users Is Not Mapped [From Users]
Spring Data JPA Problem With Updating Multiple Entities
How to Do Post Request With Raw Data Via Spring Rest Template