jackson parse pojo to json - json

I need to transform a java object versementDTO to json string Historique, this DTO contains some Dates, jackson is transforming dates to an object like that : "dateValidation":{"nano":0,"year":2007,"monthValue":2,"dayOfMonth":7,"hour":15,"minute":21,"second":24,"month":"FEBRUARY","dayOfWeek":"WEDNESDAY","dayOfYear":38,"chronology":{"id":"ISO","calendarType":"iso8601"}}, and I need to get a value like : "2007/02/21 15:21:24"
and I get the following error :
Resolved
[org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Cannot deserialize value of type `java.time.LocalDateTime` from String "2007-02-07T15:21:24": Failed to deserialize java.time.LocalDateTime: (java.time.format.DateTimeParseException) Text '2007-02-07T15:21:24' could not be parsed at index 10; nested exception is com.fasterxml.jackson.databind.exc.InvalidFormatException: Cannot deserialize value of type `java.time.LocalDateTime` from String "2007-02-07T15:21:24": Failed to deserialize java.time.LocalDateTime: (java.time.format.DateTimeParseException) Text '2007-02-07T15:21:24' could not be parsed at index 10
at [Source: (PushbackInputStream); line: 1, column: 95] (through reference chain: aws.lbackend.dto.VersementDTO["dateValidation"])]
appriciating your help !
public static String historizeInJson(VersementDTO pojo) {
SimpleDateFormat df = new SimpleDateFormat("dd-MM-yyyy hh:mm");
df.setTimeZone(TimeZone.getTimeZone("UTC"));
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setDateFormat(new StdDateFormat().withColonInTimeZone(true));
objectMapper.configure(SerializationConfig.Feature.WRITE_DATES_AS_TIMESTAMPS, false);
objectMapper.configure(SerializationConfig.Feature.FAIL_ON_EMPTY_BEANS, false);
try {
String jsVDTO = objectMapper.writeValueAsString(pojo);
//System.out.print("json dz : "+ jsVDTO);
return jsVDTO;
} catch (JsonProcessingException e) {
LOGGER.info("failed conversion: Pfra object to Json", e);
return null;
} catch (IOException e) {
throw new RuntimeException(e);
}
}

ObjectMapper objectMapper = new ObjectMapper();
SimpleModule module = new SimpleModule("ModuleDate2Json",
new Version(1, 0, 0, null));
module.addSerializer(LocalDateTime.class, new LocalDateSerializer<LocalDateTime>());
objectMapper.registerModule(module);
with LocalDateSerialzer class:
public class LocalDateSerializer<L> extends JsonSerializer<LocalDateTime {
public LocalDateSerializer() {
super();
}
#Override
public void serialize(LocalDateTime localDateTime, org.codehaus.jackson.JsonGenerator jsonGenerator, org.codehaus.jackson.map.SerializerProvider serializerProvider) throws IOException, JsonProcessingException {
jsonGenerator.writeString(localDateTime.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME));
}
}

Related

Custom Json Deserializer for a generic class type

Consider a json of type "Clothing":
{
"id":"123",
"version":2,
"apparel":{
"category":[
{
"id":"a1",
"style":"top",
"comments":[
{
"header":{
"type":"apparel.detail.Summary",
"major_version":1,
"minor_version":0
},
"summary": "notes"
}]
}
]
},
"accessories":[
{
"header":{
"type":"accessories.detail.Handbag",
"major_version":1,
"minor_version":0
},
"details":{
"brand":"Gucci",
"sno.":"G12"
},
"color":"Red",
},
{
"header":{
"type":"accessories.detail.Hat",
"major_version":1,
"minor_version":0
},
"details":{
"brand":"Adidas",
"sno.":"A12"
}
}
]
}
"Clothing" is not accessible to me and I cannot add any field level or class level json annotations.
There is a property "header" in json that helps me to determine the type of class I want to convert that entity into. I will remove the header from my json once the class type is determined (since header is not defined in my target class type because of which deserialization will fail)
I need to write a custom deserializer that returns a generic class type object. It will check if there is header, fetch target class name, remove header and deserialize it to the fetched target class and return.
This is the code that I have written, but it does not work and I am not even sure if it is possible to have a custom deserializer injected in SimpleModule with a generic return type.
#Singleton
#Provides
private Transformer provideTransformer(final HeaderDeserializer headerDeserializer) {
final SimpleModule simpleModule = new SimpleModule();
simpleModule.addDeserializer(Object.class, headerDeserializer);
mapper.registerModule(simpleModule);
}
#Singleton
#Provides
private HeaderDeserializer provideHeaderDeserializer(final ObjectMapper objectMapper) {
return new HeaderDeserializer(objectMapper);
}
#Singleton
#Provides
private ObjectMapper provideObjectMapper() {
final ObjectMapper mapper = new ObjectMapper()
// Tell object mapper how to handle joda-time.
.registerModule(new JodaModule())
// include non-null values only
.setSerializationInclusion(Include.NON_NULL)
// ensures that timezone is preserved
.disable(DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE);
return mapper;
}
My HeaderDeserializer looks something like this:
public class HeaderDeserializer<T> extends StdDeserializer<T> {
private static final long serialVersionUID = 1L;
private final ObjectMapper mapper;
public HeaderDeserializer(final ObjectMapper mapper) {
this(null, mapper);
}
public HeaderDeserializer(final Class<?> vc, final ObjectMapper mapper) {
super(vc);
this.mapper = mapper;
}
#Override
public T deserialize(final JsonParser jp, final DeserializationContext ctx) {
Object value = null;
try {
JsonNode node = this.mapper.readTree(jp);
JsonNode header = node.get("header");
if (node.has("header")) {
String targetClass = header.get("type").textValue();
removeHeaderFromJsonDoc(node);
value = this.mapper.readValue(jp, Class.forName(targetClass));
}
} catch (final IOException e) {
throw new UncheckedIOException(e);
} catch (final ClassNotFoundException e) {
// do somehting
}
return (T) value;
}
private void removeHeaderFromJsonDoc(final JsonNode document) {
final Iterator<Entry<String, JsonNode>> itr = document.fields();
while (itr.hasNext()) {
final Entry<String, JsonNode> childNodeEntry = itr.next();
if (childNodeEntry.getKey().equals("header")) {
itr.remove();
}
}
}
}
And my main deserializer which will use the custom deserializer defined above looks like:
public final Clothing deserialize(
final String stringValue,
final Class<? extends Clothing> clazz) {
try {
return this.objectMapper.readValue(stringValue, clazz);
} catch (final IOException e) {
throw new IllegalArgumentException();
}
}
this.objectMapper.readValue(stringValue, clazz);
Class type of 'clazz' in this readValue method should match class type passed in simpleModule.addDeserializer.
It is not going inside your deserializer because you are adding deserializer to SimpleModule for 'Object' class and reading value for different class passed to 'Clothing deserialize',

Spring Boot - Encrypt JSON data

In our application we have to encrypt/decrypt the Json property values (not the property name) for each request and response.
Example,
{"userName":"encrypted value", "email":"encrypted value"}
We use Sprint boot 1.3 and we are using #RequestBody and #ResponseBody annotations to bind the request json with the object and serialise the response object as JSON.
We don't want to call encrypt/decrypt method in our each controller method. Is there any way we can instruct sprint to decrypt the json values before binding with the request object? Similarly, to encrypt the response object field values before converting them to json? Or customising Jackson may help us?
Thanks!
You can write your own http message converter. Since you are using spring boot it would be quite easy: just extend your custom converter from AbstractHttpMessageConverter and mark the class with #Component annotation.
From spring docs:
You can contribute additional converters by simply adding beans of that type in a Spring Boot context. If a bean you add is of a type that would have been included by default anyway (like MappingJackson2HttpMessageConverter for JSON conversions) then it will replace the default value.
And here is a simple example:
#Component
public class Converter extends AbstractHttpMessageConverter<Object> {
public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
#Inject
private ObjectMapper objectMapper;
public Converter(){
super(MediaType.APPLICATION_JSON_UTF8,
new MediaType("application", "*+json", DEFAULT_CHARSET));
}
#Override
protected boolean supports(Class<?> clazz) {
return true;
}
#Override
protected Object readInternal(Class<? extends Object> clazz,
HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
return objectMapper.readValue(decrypt(inputMessage.getBody()), clazz);
}
#Override
protected void writeInternal(Object o, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
outputMessage.getBody().write(encrypt(objectMapper.writeValueAsBytes(o)));
}
private InputStream decrypt(InputStream inputStream){
// do your decryption here
return inputStream;
}
private byte[] encrypt(byte[] bytesToEncrypt){
// do your encryption here
return bytesToEncrypt;
}
}
Okay, so I used #eparvan 's answer and made few modifications.
Create a component that encrypts the JSON response and decrypt the request params from frontend.
I am fetching request params in encrypted format in "data" object something like this and also sending the encrypted response in the same way data object.
reference response:
{"data":"requestOrResponseInEncryptedUsingPrivateKey"}
#Component
public class Converter extends AbstractHttpMessageConverter<Object> {
private static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;
#Autowired
private ObjectMapper objectMapper;
public Converter() {
super(MediaType.APPLICATION_JSON,
new MediaType("application", "*+json", DEFAULT_CHARSET));
}
#Override
protected boolean supports(Class<?> clazz) {
return true;
}
#Override
protected Object readInternal(Class<? extends Object> clazz,
HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
return objectMapper.readValue(decrypt(inputMessage.getBody()), clazz);
}
#Override
protected void writeInternal(Object o, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
outputMessage.getBody().write(encrypt(objectMapper.writeValueAsBytes(o)));
}
/**
* requests params of any API
*
* #param inputStream inputStream
* #return inputStream
*/
private InputStream decrypt(InputStream inputStream) {
//this is API request params
StringBuilder requestParamString = new StringBuilder();
try (Reader reader = new BufferedReader(new InputStreamReader
(inputStream, Charset.forName(StandardCharsets.UTF_8.name())))) {
int c;
while ((c = reader.read()) != -1) {
requestParamString.append((char) c);
}
} catch (IOException e) {
e.printStackTrace();
}
try {
//replacing /n if available in request param json string
//reference request: {"data":"thisisencryptedstringwithexpirytime"}
JSONObject requestJsonObject = new
JSONObject(requestParamString.toString().replace("\n", ""));
String decryptRequestString = EncryptDecrypt.decrypt(requestJsonObject.getString("data"));
System.out.println("decryptRequestString: " + decryptRequestString);
if (decryptRequestString != null) {
return new ByteArrayInputStream(decryptRequestString.getBytes(StandardCharsets.UTF_8));
} else {
return inputStream;
}
} catch (JSONException err) {
Log.d("Error", err.toString());
return inputStream;
}
}
/**
* response of API
*
* #param bytesToEncrypt byte array of response
* #return byte array of response
*/
private byte[] encrypt(byte[] bytesToEncrypt) {
// do your encryption here
String apiJsonResponse = new String(bytesToEncrypt);
String encryptedString = EncryptDecrypt.encrypt(apiJsonResponse);
if (encryptedString != null) {
//sending encoded json response in data object as follows
//reference response: {"data":"thisisencryptedstringresponse"}
Map<String, String> hashMap = new HashMap<>();
hashMap.put("data", encryptedString);
JSONObject jsob = new JSONObject(hashMap);
return jsob.toString().getBytes();
} else
return bytesToEncrypt;
}
}
Here is my EncryptDecrypt class where encryption and decryption is going on
class EncryptDecrypt {
static String encrypt(String value) {
try {
IvParameterSpec iv = new IvParameterSpec(Constants.Encryption.INIT_VECTOR.getBytes(StandardCharsets.UTF_8));
SecretKeySpec skeySpec = new
SecretKeySpec("PRIVATE_KEY_FOR_ENCRYPTION_OR_DECRYPTION"
.getBytes(StandardCharsets.UTF_8), "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
cipher.init(Cipher.ENCRYPT_MODE, skeySpec, iv);
byte[] encrypted = cipher.doFinal(value.getBytes());
byte[] original = Base64.getEncoder().encode(encrypted);
return new String(original);
} catch (Exception ex) {
ex.printStackTrace();
}
return null;
}
static String decrypt(String encrypted) {
try {
IvParameterSpec iv = new IvParameterSpec(Constants.Encryption.INIT_VECTOR
.getBytes(StandardCharsets.UTF_8));
SecretKeySpec skeySpec = new SecretKeySpec("PRIVATE_KEY_FOR_ENCRYPTION_OR_DECRYPTION".
getBytes(StandardCharsets.UTF_8), "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
cipher.init(Cipher.DECRYPT_MODE, skeySpec, iv);
byte[] original = cipher.doFinal(Base64.getDecoder().decode(encrypted));
return new String(original);
} catch (Exception ex) {
ex.printStackTrace();
}
return null;
}
}
And you're done!

JAXB MOXy marshalling and unmarshalling of JSON keys containing colons

I am using Eclipse MOXy 2.6 and I have a very intractable problem during JSON marshalling and unmarshalling. I must produce and consume the following JSON object, respectively:
{
"ssh::server::storeconfigs_enabled": true
}
Unfortunately, the key contains colons which collidates with the XML namespacing separator.
I use the following annotated domain class SshOptions:
#XmlRootElement
public class SshOptions {
#XmlElement(name = "ssh::server::storeconfigs_enabled")
private boolean storeConfigs;
public boolean isStoreConfigs() {
return storeConfigs;
}
public void setStoreConfigs(boolean storeConfigs) {
this.storeConfigs = storeConfigs;
}
}
I have used the following test code:
public class Test {
public static void main(String[] args) {
SshOptions options = new SshOptions();
options.setStoreConfigs(true);
try {
Map<String, Object> properties = new HashMap<String, Object>();
properties.put(JAXBContextProperties.JSON_NAMESPACE_SEPARATOR, '.');
properties.put(JAXBContextProperties.MEDIA_TYPE, MediaType.APPLICATION_JSON);
properties.put(JAXBContextProperties.JSON_INCLUDE_ROOT, false);
JAXBContext context = JAXBContextFactory.createContext(new Class[] { SshOptions.class }, properties);
Marshaller marshaller = context.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
StringWriter output = new StringWriter();
marshaller.marshal(options, output);
System.out.println(output.toString());
} catch (JAXBException e) {
e.printStackTrace();
}
}
}
The output is:
{
"null" : true
}
Has anybody an idea how to marshall and unmarshal keys from and to JSON that have one or more colons in their names using Eclipse MOXy 2.6?

Telling Retrofit to what variable it should map a certain json field?

The REST API I'm talking to is responding to some of the requests in a structure as such:
{
"_links": {
"next": "NEXT_DATA_BLOCK_URL",
"prev": "PREV_DATA_BLOCK_URL",
"self": "CURRENT_DATA_BLOCK_URL"
},
"RESPONSE_DATA_NAME": [
{
... DATA_FIELDS ...
}
]
}
Where 'RESPONSE_DATA_NAME' is the data "name" - changes according to desired request. for example, it might be 'teams' or 'messages'.
Therefore I created a generic class with the following members:
public class PagedResponse<T> {
public PagingLinks _links;
public List<T> _data;
}
Is there any way I can set up my RestAdapter so that it'll always map 'RESPONSE_DATA_NAME' to the '_data' member, no matter what the field name actually is?
Thanks ^_^
Using gson you can annotate your _data field with the #SerializedName. The parameter (value) of this annotation is the name to be used when serialising and deserialising objects. For example, the Java field _data is represented as RESPONSE_DATA_NAME in JSON.
public class PagedResponse<T> {
public PagingLinks _links;
#SerializedName(value="RESPONSE_DATA_NAME")
public List<T> _data;
}
Further see doc
If you want to control the json field then you have to write custom de-serializer as like below
public class CustomDeserializer implements JsonDeserializer<PagedResponse> {
#Override
public PagedResponse deserialize(final JsonElement json,
final Type typeOfT, final JsonDeserializationContext context)
throws JsonParseException {
Gson gson = new Gson();
PagedResponse pagedResponse = new PagedResponse<>();
List list = new ArrayList<>();
pagedResponse = gson.fromJson(json, PagedResponse.class);
Type listType = new TypeToken<List>() {}.getType();
Set<Entry<String, JsonElement>> enteries = json.getAsJsonObject().entrySet();
for (Entry<String, JsonElement> entry : enteries) {
JsonElement jsonElement = (JsonElement) entry.getValue();
if (jsonElement.isJsonArray()) {
list.add(gson.fromJson(jsonElement, listType));
}
}
pagedResponse.set_data(list);
return pagedResponse;
}
}
finally parse it
GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.registerTypeAdapter(PagedResponse.class, new CustomDeserializer());
Gson gson = gsonBuilder.create();
gson.fromJson(Your_JSON_STRING_HERE, PagedResponse.class);
So I finally found a solution to the problem...
I created a costume de-serializer, which adds the data field to the existing JsonObject, and copies the content of the RESPONSE_DATA_NAME (which is a JsonArray).
Then I serialize it normaly with GSON simple conversion (gson.fromJson()).
It's a bit stupid but it works =P
The de-serializer's class:
public class PagedResponseDeserializer implements JsonDeserializer<PagedResponse> {
#Override
public PagedResponse deserialize(JsonElement json, Type typeOfT,
JsonDeserializationContext context) throws JsonParseException {
Gson gson = new Gson();
JsonElement value = null;
JsonObject jsonObject = json.getAsJsonObject();
Iterable<Entry<String,JsonElement>> entries = jsonObject.entrySet();
for (Entry<String, JsonElement> entry : entries) {
value = entry.getValue();
if (value.isJsonArray()) break;
}
jsonObject.add("data", value);
return gson.fromJson(jsonObject, typeOfT);
}
}

Jackson Json Object mapper : how to map an array?

I am trying to map an array from a backend api call. How can I map this data knowing that :
the following classes will be used to hold the json array data :
#Data
private static class UserListBean {
private List<UserBean> userList;
}
#Data
private static class UserBean {
private String id;
private String userName;
private String password;
}
the json will have the following format (the following example just have one item in it) :
[
{
"id":1,
"userName":"bob",
"password":"403437d5c3f70b1329f37a9ecce02adbbf3e986"
}
]
I am using Jackson and I have tried the following so far but it keeps sending me back an exception
final ObjectMapper mapper = new ObjectMapper();
mapper.configure(Feature.FAIL_ON_UNKNOWN_PROPERTIES, false);
final ObjectReader reader = mapper.reader(UserListBean.class);
GeoHubAccountListBean accounts = null;
try {
accounts = reader.readValue(jsonString);
} catch (final IOException ex) {
log.error("Cannot convert JSON into a list of users", ex);
}
Here the final ObjectReader reader = mapper.reader(UserListBean.class); throws an exception
Can not deserialize instance of com.xxx.XXX$UserListBean out of START_ARRAY token
Any idea ?
thanks
Well, if you are trying to deserialize json to an object of type UserListBean, then you need to deserialize a JSONObject (Java Objects tend to map to JSONObjects).
Therefore, your outer most json construct should be an object. Your outer most json construct is a JSONArray.
Your UserListBean has a single field, which is a List<UserBean>. So your top level json construct (which is a JSONObject) should contain a single field with the name 'userList' with a value that is a JSONArray (Java Collections tend to map to JSONArrays).
I think this is the actual json you are looking for:
{
"userList":[
{
"id":1,
"userName":"bob",
"password":"403437d5c3f70b1329f37a9ecce02adbbf3e986"
}
]
}
If you have no control over the json coming in, then you probably want to ditch the parent object UserListBean and deal directly with the List<UserBean>, as that would work with the json you have provided.
Try something like the following:
public static <T> T mapFromJson(String json, Class<T> clazz) throws JsonParseException, JsonMappingException, IOException {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(new JavaTimeModule());
objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
return objectMapper.readValue(json, clazz);
}
public static void main(String[] args) throws IOException {
HeaVO HeaVO=new HearVO();
HeaVO.setId("Id");
List<HeaVO> listVO=new ArrayList<HeaVO>();
listVO.add(HeaVO);
List<HeaVO> listHeaVORes=Arrays.asList(mapFromJson(mapToJson(listHeaVO), HeaVO[].class));
System.out.println(((HeaVO)listVORes.get(0)).getId());
}