In my pojo class I have configured a CustomDeserializer with annotation
#JsonDeserialize(using = CustomDeserializer.class)
class Myclass {
private String A;
#JsonIgnore
private String B;
#JsonIgnore
private String C;
private String D;
...
private String Z;
/*getters and setters*/
}
In CustomDeserializer, I want to manage only some of the fields and leave the rest for Jackson to manage.
CustomDeserializer.java
public class CustomDeserializer extends StdDeserializer<Myclass > {
private static final long serialVersionUID = 4781685606089836048L;
public CustomDeserializer() {
super(Myclass.class);
}
#Override
public Myclass deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, IllegalResponseException {
ObjectMapper mapper = (ObjectMapper) jp.getCodec();
ObjectNode root = (ObjectNode) mapper.readTree(jp);
Myclass myClass = mapper.readValue(root.toString(), Myclass.class);
//--- HERE MANAGE FIELD B ---
myClass.setB(myNewB);
//--- HERE MANAGE FIELD C ---
myClass.setC(myNewC);
return myClass;
}
}
This way I run into an infinite loop because of the following line:
mapper.readValue(root.toString(), Myclass.class);
Is there a way to set default behavior when using Jackson so that I can exclude my CustomDeserializer?
The problem is that you will need a fully constructed default deserializer; and this requires that one gets built, and then your deserializer gets access to it. DeserializationContext is not something you should either create or change; it will be provided by ObjectMapper.
To meet your requirement you can start by writing a BeanDeserializerModifier and registering it via SimpleModule.
The following example should work:
public class CustomDeserializer extends StdDeserializer<Myclass> implements ResolvableDeserializer
{
private static final long serialVersionUID = 7923585097068641765L;
private final JsonDeserializer<?> defaultDeserializer;
public CustomDeserializer (JsonDeserializer<?> defaultDeserializer)
{
super(Myclass.class);
this.defaultDeserializer = defaultDeserializer;
}
#Override public Myclass deserialize(JsonParser jp, DeserializationContext ctxt)
throws IOException, JsonProcessingException
{
Myclass deserializedMyclass = (Myclass) defaultDeserializer.deserialize(jp, ctxt);
// custom logic
return deserializedMyclass;
}
// You have to implement ResolvableDeserializer when modifying BeanDeserializer
// otherwise deserializing throws JsonMappingException
#Override public void resolve(DeserializationContext ctxt) throws JsonMappingException
{
((ResolvableDeserializer) defaultDeserializer).resolve(ctxt);
}
public static void main(String[] args) throws JsonParseException, JsonMappingException, IOException
{
SimpleModule module = new SimpleModule();
//Writing a new BeanDeserializerModifier
module.setDeserializerModifier(new BeanDeserializerModifier()
{
#Override public JsonDeserializer<?> modifyDeserializer(DeserializationConfig config, BeanDescription beanDesc, JsonDeserializer<?> deserializer)
{
if (beanDesc.getBeanClass() == Myclass.class)
return new CustomDeserializer(deserializer);
return deserializer;
}
});
//register the BeanDeserializerModifier via SimpleModule
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(module);
Myclass myclass = mapper.readValue(new File("d:\\test.json"), Myclass.class);
}
}
Related
I want to convert a json into Java class by having custom deserializer.
I'm able to serialize ACC_NUM, NAME and any other fields from json but not sure what can be done to convert MOBILE_NUMBER_1,MOBILE_NUMBER_2 like fields into single JSONArray(See AccountInfo class). There can be many more fields like this and count also is not fixed. Example there can be ADDRESS_1, ADDRESS_2 till ADDRESS_20 and so on and all this fields should go in JSONArray of ADDRESS after deserilization.
I have a Map of Map which holds info like this:
{
"accountInfo": {
"ACC_NUM": "1234567890",
"NAME": "John Cena",
"MOBILE_NUMBER_1": "12376534",
"MOBILE_NUMBER_2": "12376534",
"MOBILE_NUMBER_3": "12376534",
"MOBILE_NUMBER_4": "12376534"
},
"someOther": {
//similer to above
}
}
This info I want to convert to the following class CommonInfo:
public class CommonInfo {
private AccountInfo accountInfo;
//other properties...
}
#JsonIgnoreProperties(ignoreUnknown = true)
public class AccountInfo {
#JsonProperty("ACC_NUM")
private FieldValue<BigInteger> accountNum;
#JsonProperty("NAME")
private FieldValue<String> name;
#JsonProperty("MOBILE_NUMBER")
private FieldValue<JSONArray> mobileNumber;
}
//FieldValue class
public interface FieldValue<T> {
T getInitialValue();
void setInitialValue(T initialValue);
T getValue();
void setValue(T value);
}
#JsonInclude(JsonInclude.Include.ALWAYS)
public class FieldValueImpl<T> implements FieldValue<T> {
protected T initialValue;
protected T value;
//getters, setters, cons..
}
My service code takes json/Map and tries to convert it to CommonInfo class
#Service
public class MyService {
private final ObjectMapper jsonMapper = new ObjectMapper();
#PostConstruct
protected void init() {
SimpleModule simpleModule = new SimpleModule();
simpleModule.addSerializer(FieldValue.class, new FieldValueSerializer());
simpleModule.addDeserializer(FieldValue.class, new FieldValueDeserializer());
jsonMapper.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true);
jsonMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
jsonMapper.registerModule(simpleModule);
}
public CommonInfo setPojoResult(Map<String, LinkedHashMap<String, String>> contentAsMap) {
return jsonMapper.convertValue(contentAsMap, CommonInfo.class);
}
}
Serializer and Deserializer looks like this:
public class FieldValueDeserializer extends JsonDeserializer<FieldValue<?>> implements ContextualDeserializer {
private JavaType valueType;
public JsonDeserializer<?> createContextual(DeserializationContext ctxt, BeanProperty property)
throws JsonMappingException {
var deserializer = new FieldValueDeserializer();
if (property == null) {
deserializer.valueType = ctxt.getContextualType().containedType(0);
} else {
var wrapperType = property.getType();
var valueType = wrapperType.containedType(0);
deserializer.valueType = valueType;
}
return deserializer;
}
#Override
public FieldValue<?> deserialize(JsonParser parser, DeserializationContext context) throws IOException {
FieldValueDeserializer deserializer = new FieldValueDeserializer();
deserializer.getKnownPropertyNames();
FieldValue<?> fieldValueImpl = new FieldValueImpl<>();
if (valueType.toString().contains("java.time.LocalDate")) {
JsonNode node = parser.getCodec().readTree(parser);
FieldValue<LocalDate> f1 = new FieldValueImpl<>();
f1.setValue(DateUtils.convertJulianToLocalDate(node.textValue()));
return f1;
} else {
fieldValueImpl.setValue(context.readValue(parser, valueType));
}
return fieldValueImpl;
}
}
//--
public class FieldValueSerializer extends StdSerializer<FieldValue> {
public FieldValueSerializer() {
this(null);
}
public FieldValueSerializer(Class<FieldValue> vc) {
super(vc);
}
#Override
public void serialize(FieldValue value, JsonGenerator jgen, SerializerProvider provider) throws IOException {
jgen.writeString(String.valueOf(value.getCurValue()));
}
}
public class user {
private String planId;
private String eid;
#JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
private Collection<String> userIds;
}
I have a pojo like above
and the code which is use for creating the json request object is this :
public UserCollection getUserCollection(final user args) {
Map<String, String> headersMap = new HashMap<>();
ObjectMapper jacksonMapper = new ObjectMapper();
jacksonMapper.disable(MapperFeature.USE_ANNOTATIONS); // this line is creating the userIds field to reflect
//By any code we can remove the userId field from the args object
String responseBody = null;
String responseStatus = null;
String jsonRequestBody = jacksonMapper.writeValueAsString(args);
}
I just want to remove userIds from the args by not removing any of the above code.
Thanks in advance.
I don't know how you should solve this without removing the annotation processing code, you should maybe add a custom serializer. You can read further about the topic at here.
public class HelloWorld {
public static void main(String[] args) throws JsonProcessingException {
User myItem = new User("planId", "eId", List.of("1", "2"));
ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
module.addSerializer(User.class, new UserSerializer());
mapper.registerModule(module);
String serialized = mapper.writeValueAsString(myItem);
}
#Data
#AllArgsConstructor
public static class User {
private String planId;
private String eId;
private Collection<String> userIds;
}
public static class UserSerializer extends StdSerializer<User> {
public UserSerializer() {
this(null);
}
public UserSerializer(Class<User> t) {
super(t);
}
#Override
public void serialize(User value, JsonGenerator gen, SerializerProvider provider) throws IOException{
gen.writeStartObject();
gen.writeStringField("planId", value.planId);
gen.writeStringField("eId", value.eId);
gen.writeEndObject();
}
}
}
If you had to use it without annotations, and cannot add a custom serializer you should map the User class into a more specific class without the field in question and then serialize that one.
I am trying to write Jackson deserializer module in Spring Boot app.
The main reason is to encrypt pin number from incoming request by using custom Jackson deserializer.
Encryption properties are provided by spring component CipherInterface
I was trying solution from this but my custom deserializer still was not called.
Instead of this based StringDeserializer is always called and no encryption is performed
Thanks in advance
Annotation:
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.FIELD)
#JacksonAnnotation
public #interface Encrypted {
}
Request body with field to be encrypted
#Value
public class CardCounterDecreaseRequest {
#Encrypted
private final String pinValue;
}
Jackson configuration
#Bean
ObjectMapper unrestrictObjectMapper(final CipherInterface cipherInterface) {
return JsonMapper.builder()
.enable(MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS)
.enable(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES)
.enable(SerializationFeature.INDENT_OUTPUT)
.enable(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES)
.enable(DeserializationFeature.FAIL_ON_NUMBERS_FOR_ENUMS)
.disable(DeserializationFeature.FAIL_ON_MISSING_EXTERNAL_TYPE_ID_PROPERTY)
.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
.disable(DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE)
.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS)
.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
.disable(DeserializationFeature.ACCEPT_FLOAT_AS_INT)
.visibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.NONE)
.visibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY)
.addModules(new EncryptionModule(cipherInterface), new JavaTimeModule(), new VavrModule(), new ParanamerModule())
.build();
}
Module:
public class EncryptionModule extends SimpleModule {
private final CipherInterface cipher;
public EncryptionModule(final CipherInterface cipher) {
super();
this.cipher = cipher;
}
#Override
public void setupModule(final SetupContext context) {
context.addBeanDeserializerModifier(new EncryptedDeserializerModifier(cipher));
}
}
public class EncryptedDeserializerModifier extends BeanDeserializerModifier {
private final CipherInterface cipher;
public EncryptedDeserializerModifier(final CipherInterface cipher) {
super();
this.cipher = cipher;
}
#Override
public BeanDeserializerBuilder updateBuilder(final DeserializationConfig config,
final BeanDescription beanDesc,
final BeanDeserializerBuilder builder) {
final Iterator<SettableBeanProperty> it = builder.getProperties();
while (it.hasNext()) {
final SettableBeanProperty prop = it.next();
if (null != prop.getAnnotation(Encrypted.class)) {
final JsonDeserializer<Object> current = prop.getValueDeserializer(); // always returns null
final EncryptedJsonDeserializer encryptedDeserializer = new EncryptedJsonDeserializer(cipher, current);
final SettableBeanProperty propWithEncryption = prop.withValueDeserializer(encryptedDeserializer);
builder.addOrReplaceProperty(propWithEncryption, true);
}
}
return builder;
}
}
And finally deserializer:
public class EncryptedJsonDeserializer extends JsonDeserializer<Object> implements ContextualDeserializer {
private final CipherInterface service;
private final JsonDeserializer<Object> baseDeserializer;
private final BeanProperty property;
public EncryptedJsonDeserializer(final CipherInterface service, final JsonDeserializer<Object> baseDeserializer) {
this.service = service;
this.baseDeserializer = baseDeserializer;
this.property = null;
}
public EncryptedJsonDeserializer(final CipherInterface service, final JsonDeserializer<Object> wrapped, final BeanProperty property) {
this.service = service;
this.baseDeserializer = wrapped;
this.property = property;
}
#Override
public Object deserialize(final JsonParser p, final DeserializationContext ctxt) throws IOException, JsonProcessingException {
JsonDeserializer<?> deserializer = baseDeserializer;
if (deserializer instanceof ContextualDeserializer) {
deserializer = ((ContextualDeserializer) deserializer).createContextual(ctxt, property);
}
return // encryption logic here
}
#Override
public JsonDeserializer<?> createContextual(final DeserializationContext ctxt, final BeanProperty property) throws JsonMappingException {
JsonDeserializer<Object> wrapped = ctxt.findRootValueDeserializer(property.getType());
return new EncryptedJsonDeserializer(service, wrapped, property);
}
Just try below code, as you had created deserializer correctly but you are not informing spring that while deserialize this entity use below Custom desierializer class.
Add this extra line #JsonDeserialize(using = EncryptedJsonDeserializer.class) and try once.
#Value
#JsonDeserialize(using = EncryptedJsonDeserializer.class)
public class CardCounterDecreaseRequest {
#Encrypted
private final String pinValue;
}
It will help you.
I'm parsing json with custom desereailizer. It reads JsonNode and then deserializes entity from json by ObjectMapper.
public static class CustomDeserializer extends StdDeserializer<Custom> implements ResolvableDeserializer {
private final JsonDeserializer<?> defaultDeserializer;
public CustomDeserializer(JsonDeserializer<?> defaultDeserializer) {
super(Custom.class);
this.defaultDeserializer = defaultDeserializer;
}
#Override
public Custom deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {
final ObjectNode node = jp.readValueAsTree();
// some node structure validation
try (final TreeTraversingParser newJsonParser = new TreeTraversingParser(node)) {
newJsonParser.nextToken();
return (Custom) defaultDeserializer.deserialize(newJsonParser, ctxt);
}
}
#Override
public void resolve(DeserializationContext ctxt) throws JsonMappingException {
((ResolvableDeserializer) defaultDeserializer).resolve(ctxt);
}
}
On some requests it works, on other throws
com.fasterxml.jackson.core.json.UTF8StreamJsonParser cannot be cast to com.fasterxml.jackson.databind.node.TreeTraversingParser
or
m.fasterxml.jackson.core.json.ReaderBasedJsonParser cannot be cast to com.fasterxml.jackson.databind.node.TreeTraversingParser
How to prevent that case?
I'm trying to custom deserialize incoming json to the field, but #JsonDeserialize is not working wheh I put it above the setter of the field I want to deserialize.
public class MyEntity {
#JsonProperty("identifier")
private String identifier;
#JsonDeserialize(using = IdentifierDeserializer.class)
public void setIdentifier(String identifier)
{
this.identifier = identifier;
}
}
....
public class IdentifierDeserializer
extends JsonDeserializer<String>
{
#Override
public String deserialize(JsonParser jp, DeserializationContext ctxt)
throws IOException
{
ObjectCodec oc = jp.getCodec();
JsonNode node = oc.readTree(jp);
final JsonNode entity = node.get("entity");
return entity.get("id").asText();
}
}
Im doing this via _restTemplate httpMessageConverter flow, just standar API calls, and no, it does not throw any exceptions, it just not invoked, because I this this with debug and my breakpoints left untouched.
HttpEntity<T> httpEntity = new HttpEntity<>(entity, httpHeaders());
MappingJackson2HttpMessageConverter jsonConverter = new MappingJackson2HttpMessageConverter();
jsonConverter.setSupportedMediaTypes(Lists.newArrayList(MediaType.APPLICATION_JSON_UTF8));
_restTemplate.getMessageConverters().add(jsonConverter);
return _restTemplate.exchange(url, method, httpEntity, MyEntity.class).getBody();
But #JsonDeserialize does work when I put it above the class. So what's the problem guys? Thanks.