I am writing a class JsonUtils which will contain different functions to serialize and deserialize data.
public class JsonUtils {
private static final ObjectMapper JSON_MAPPER = new ObjectMapper();
public static String toJsonString(Object obj) {
String json = null;
JSON_MAPPER.setPropertyNamingStrategy(new CustomNamingStrategy());
JSON_MAPPER.setSerializationInclusion(Inclusion.NON_NULL);
try {
System.out.print("OBJECT MAPPER:---> JSON STRING:\n" + JSON_MAPPER.writerWithDefaultPrettyPrinter().writeValueAsString(obj));
json = JSON_MAPPER.writerWithDefaultPrettyPrinter().writeValueAsString(obj);
} catch (JsonGenerationException e) {
e.printStackTrace();
} catch (JsonMappingException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return json;
}
public static <T> T toPOJO(String json, Class<T> type){
JSON_MAPPER.setPropertyNamingStrategy(new CustomNameNamingStrategy());
System.out.println("TO POJO: Json string " + json);
try {
return JSON_MAPPER.readValue(json, type);
} catch (JsonParseException e) {
e.printStackTrace();
} catch (JsonMappingException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
Now, I want the functions to be used generically. For ex: someone wants to call toJsonString method but wants to use a different naming strategy to convert to json. Or may want to add some other properties to ObjectMapper like register a module.
Currently, the ObjectMapper properties are being set inside the function, thus a new naming strategy or a different property for ObjectMapper can't be used.
Is there a way that every user for JsonUtils initially sets it's own properties for ObjectMapper ? Or a efficient and generic way to write my Utility class ?
You could use something like this:
ObjectMapperProperties.java
package example;
import com.fasterxml.jackson.databind.PropertyNamingStrategy;
public class ObjectMapperProperties {
private PropertyNamingStrategy propertyNamingStrategy;
private ObjectMapperProperties(final PropertyNamingStrategy propertyNamingStrategy) {
this.propertyNamingStrategy = propertyNamingStrategy;
}
public PropertyNamingStrategy getPropertyNamingStrategy() {
return propertyNamingStrategy;
}
public static class ObjectMapperPropertiesBuilder {
private PropertyNamingStrategy builderPropertyNamingStrategy;
public ObjectMapperPropertiesBuilder() {
}
public ObjectMapperPropertiesBuilder setPropertyNamingStrategy(final PropertyNamingStrategy builderPropertyNamingStrategy) {
this.builderPropertyNamingStrategy = builderPropertyNamingStrategy;
return this;
}
public ObjectMapperProperties build() {
return new ObjectMapperProperties(builderPropertyNamingStrategy);
}
}
}
ObjectMapperFactory.java
package example;
import com.fasterxml.jackson.databind.ObjectMapper;
public class ObjectMapperFactory {
public static ObjectMapper getObjectMapper(final ObjectMapperProperties objectMapperProperties) {
final ObjectMapper result = new ObjectMapper();
result.setPropertyNamingStrategy(objectMapperProperties.getPropertyNamingStrategy());
return result;
}
}
Client.class
package example;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.PropertyNamingStrategy;
import example.ObjectMapperProperties.ObjectMapperPropertiesBuilder;
public class Client {
public static void main(String[] args) {
ObjectMapperPropertiesBuilder objectMapperPropertiesBuilder = new ObjectMapperPropertiesBuilder();
objectMapperPropertiesBuilder.setPropertyNamingStrategy(PropertyNamingStrategy.CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES);
ObjectMapperFactory factory = new ObjectMapperFactory();
ObjectMapper objectMapper = ObjectMapperFactory.getObjectMapper(objectMapperPropertiesBuilder.build());
}
}
Than you could create ObjectMapper with setting as you need it. It doesn't have a sense and it's error-prone to set properties twice on already created instance.
private static final ObjectMapper JSON_MAPPER = new ObjectMapper();
JSON_MAPPER.setSerializationInclusion(Inclusion.NON_NULL);
so next time you need to reset this property for example, but to create new ObjectMapper() through some factory is priceless and less error-prone
Answers:
No you will create new instance of ObjectMapper through ObjectMapperFactory for each call and just pass the ObjectMapperProperties.
public static String toJsonString(Object obj,final ObjectMapperProperties objectMapperProperties) {
ObjectMapper objectMapper = ObjectMapperFactory.getObjectMapper(objectMapperProperties);
}
In case you don't want to create new ObjectMapper instance and properties are final (mean you will always create ObjectMapper with same properties) than make a method.
public static String toJsonString(Object obj, ObjectMapper objMapper) {}
Second question see Builder Pattern
For better testing variation with Factory as interface will be helpful:
ObjectMapperFactory.class
public interface ObjectMapperFactory {
public ObjectMapper getObjectMapper(final ObjectMapperProperties objectMapperProperties) {
}
Implementation of ObjectMapperFactory
ObjectMapperFactoryImpl.class
package example;
import com.fasterxml.jackson.databind.ObjectMapper;
public class ObjectMapperFactoryImpl implements ObjectMapperFactory {
public ObjectMapper getObjectMapper(final ObjectMapperProperties objectMapperProperties) {
final ObjectMapper result = new ObjectMapper();
result.setPropertyNamingStrategy(objectMapperProperties.getPropertyNamingStrategy());
return result;
}
}
and in your class
public class JsonUtils {
private final ObjectMapperFactory objectMapperFactory;
public JsonUtils(final ObjectMapperFactory objectMapperFactory) {
this.objectMapperFactory = objectMapperFactory;
}
}
But that is just a variantion. For your purposes answer posted above is enough.
you can use hashmap and before call, from caller function first put some settings value like this
Map <String, String>settings = new HashMap<String, String>();
settings.put("CUSTOM_NAMING_PROPERTY", "CAMEL_CASE");
and inside your function toJsonString you check the value.
public static String toJsonString(Object obj,Map settings ) {
String json = null;
if(settings.get("CUSTOM_NAMING_PROPERTY")!=null){
//put your settings here.......
}
/////....... contd.....
JSON_MAPPER.setSerializationInclusion(Inclusion.NON_NULL);
try {
System.out.print("OBJECT MAPPER:---> JSON STRING:\n" + JSON_MAPPER.writerWithDefaultPrettyPrinter().writeValueAsString(obj));
json = JSON_MAPPER.writerWithDefaultPrettyPrinter().writeValueAsString(obj);
} catch (JsonGenerationException e) {
e.printStackTrace();
} catch (JsonMappingException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return json;
}
Related
I am coding in an Spring Boot Project and there was a lot of API with diffrent Request Param so I'm trying to write a generic function with mapper an request param into a list object, then cast it into a class like the code below
public static <D> List<D> convertStringListToObject(String string) {
if (string == null) return null;
try {
return objectMapper.readValue(string, new TypeReference<>() {
});
} catch (JsonProcessingException e) {
e.printStackTrace();
}
return null;
}
But the result is it can only return a list of Object not the list of D class like I'm expected. Does anyone have any ideas how to write this function?
Eddited:
Here is how I invoke it:
filterBlockRequestDto.setPopularFiltersList(ApiUtil.convertStringListToObject(filterBlockRequestDto.getPopularFilters()));
The FilterBlockRequestDto class
import lombok.*;
import java.util.List;
#Getter
#Setter
#Builder
#ToString
#AllArgsConstructor
#NoArgsConstructor
public class FilterBlockRequestDto {
Integer locationId;
Integer projectId;
String totalBudget;
List<TotalBudgetDto> totalBudgetList;
// The string was pass in Request param
String popularFilters;
List<PopularFiltersDto> popularFiltersList;
Integer viewRating;
Integer numberOfBed;
}
One way is to accept type reference as parameter so that the caller can provide the target class and as TypeReference is a subclass, generic type information will be available at runtime.
public static <D> List<D> convertStringListToObject(String string, TypeReference<List<D>> typeReference) {
if (string == null) return null;
try {
return objectMapper.readValue(string, typeReference);
} catch (JsonProcessingException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
You'll have to pass the type you want to deserialize your string to as well..
My approach on this would be something like this:
public static <T> T convertStringListToObject(String string, Class<T> clazz) {
if (string == null) {
return null;
}
try {
return objectMapper.readValue(string, clazz);
} catch (JsonProcessingException e) {
e.printStackTrace();
}
return null;
}
and then use this method as follows:
List<Model> models =
Arrays.asList(Mapper.convertStringListToObject(stringList, Model[].class));
I need to junit test a piece of code, but the GsonConverter it calls from different class is in static method that i cannot change. I haven't a clue how to proceed as i cant mock it due to it being static.
public String fetchEntity(Object retValue, Object[] args) {
String refDet= null;
List<Details> updatedDetails = null;
if (retValue != null && retValue instanceof List && ((List) retValue).stream()
.noneMatch((o -> !(o instanceof Details)))) {
updatedDetails = (List<Details>) retValue;
} else {
logger.warn("Error");
return null;
}
try {
refDet= GsonConverter.serialize(updatedDetails );
} catch (Exception e) {
logger.warn("Error updatedDetails ");
}
return refDet;
}
Here is the class with static methods
class GsonConverter{
public static final Gson GSON = new GsonBuilder()
.registerTypeAdapter(DateTime.class, (JsonDeserializer<DateTime>) (dateTime, type, context) -> ISODateTimeFormat.dateTime().parseDateTime(dateTime.getAsString()))
.create();
public static String serialize(Object o) {
return GSON.toJson(o);
}
}
The simplest thing would be to hide the direct use of GsonConverter behind an object instance. Perhaps something like:
interface JsonMapper {
String toJsonString(Object o);
}
class GsonJsonMapper implements JsonMapper {
String toJsonString(Object o) {
return GsonConverter.serialize(o);
}
}
Now in your original code, depend on the interface (JsonMapper) but instantiate it as an GsonJsonMapper (ideally using a dependency injection framework like Guice or Spring).
// declare an instance of type JsonMapper
private JsonMapper mapper;
public String fetchEntity(Object retValue, Object[] args) {
// skip the first part ...
try {
// use the mapper
refDet = mapper.serialize(updatedDetails );
} catch (Exception e) {
logger.warn("Error updatedDetails ");
}
return refDet;
}
Now you have the ability to mock out the JsonMapper interface.
You will often encounter this type of situation -- code that was not written to be testable often must change in order to add tests. Which is why many developers practice TDD, or at least write unit tests immediately after writing the new code.
I'm writing a network class and want to be able to parse different responses to different classes (there's still one-to-one relationship but I want to have a single parseResponse() that will deal with all responses from different endpoints, and endpoint.className has the expected classType that I should map to):
private Class<?> parseResponse(StringBuilder responseContent, Endpoint endpoint) {
ObjectMapper mapper = new ObjectMapper();
try {
Class<?> object = mapper.readValue(responseContent.toString(), endpoint.className);
// endpoint.className has Class<?> type
if (object instanceof endpoint.className) {
}
} catch (IOException e) {
// handle errors
}
}
But there's an error if I write if (object instanceof endpoint.className)
Update: probably the better option is to add parse() method to Endpoint class:
public Class<?> parseResponse(String responseContent) {
// this.className has Class<?> type (e.g., Foo.class).
}
public enum Endpoint {
FOO (Foo.class),
BAR (Bar.class);
private Class<?> classType;
}
But there're still the same type errors.
You should separate JSON deserialisation from other parts of your app. You can not implement one method for all responses but you probably have a limited number of responses and you can declare some simple methods for each class. Generally, you could have only one method with declaration like below:
public <T> T deserialise(String payload, Class<T> expectedClass) {
Objects.requireNonNull(payload);
Objects.requireNonNull(expectedClass);
try {
return mapper.readValue(payload, expectedClass);
} catch (IOException e) {
throw new IllegalStateException("JSON is not valid!", e);
}
}
And now, you can deserialise all payloads you want. You need to provide JSON payload and POJO class you want to receive back.
Simple working solution which shows that concept:
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import java.io.IOException;
import java.util.Objects;
public class JsonMapper {
private final ObjectMapper mapper = new ObjectMapper();
public JsonMapper() {
// configure mapper instance if required
mapper.enable(SerializationFeature.INDENT_OUTPUT);
mapper.enable(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT);
// etc...
}
public String serialise(Object value) {
try {
return mapper.writeValueAsString(value);
} catch (JsonProcessingException e) {
throw new IllegalStateException("Could not generate JSON!", e);
}
}
public <T> T deserialise(String payload, Class<T> expectedClass) {
Objects.requireNonNull(payload);
Objects.requireNonNull(expectedClass);
try {
return mapper.readValue(payload, expectedClass);
} catch (IOException e) {
throw new IllegalStateException("JSON is not valid!", e);
}
}
public Foo parseResponseFoo(String payload) {
return deserialise(payload, Foo.class);
}
public Bar parseResponseBar(String payload) {
return deserialise(payload, Bar.class);
}
public static void main(String[] args) {
JsonMapper jsonMapper = new JsonMapper();
String bar = "{\"bar\" : 2}";
System.out.println(jsonMapper.parseResponseBar(bar));
String foo = "{\"foo\" : 1}";
System.out.println(jsonMapper.parseResponseFoo(foo));
System.out.println("General method:");
System.out.println(jsonMapper.deserialise(foo, Foo.class));
System.out.println(jsonMapper.deserialise(bar, Bar.class));
}
}
class Foo {
public int foo;
#Override
public String toString() {
return "Foo{" +
"foo=" + foo +
'}';
}
}
class Bar {
public int bar;
#Override
public String toString() {
return "Bar{" +
"bar=" + bar +
'}';
}
}
See also:
Deserializing or serializing any type of object using Jackson ObjectMapper and handling exceptions
What are Reified Generics? How do they solve Type Erasure problems and why can't they be added without major changes?
How to use jackson to deserialize to Kotlin collections
I have an interface, which I want to use for serialize/deserialize. I want to omit some of the fields. Code below is not working so far.
#JsonAutoDetect(fieldVisibility = Visibility.NONE)
public interface MyWrapper {
//no annotation to not serialize
String getMyField();
//annotation to deserialize
#JsonProperty("my_field")
void setMyField();
}
You can either specify #JsonIgnore annotation on the method, or #JsonIgnoreProperties(value = {"myfield"}) annotation on the class.
see examples here
EDIT:
which version of Jackson are you using? becuase in the one I am using (2.5) the use of #JsonIgnore together with #JsonProperty works perfectly.
also, notice that the setter needs to receive an argument to actually be used by Jackson
interface with fixed setter:
#JsonAutoDetect(fieldVisibility = Visibility.NONE)
public interface MyWrapper {
#JsonIgnore
String getMyField();
// annotation to deserialize
#JsonProperty("my_field")
void setMyField(String f);
}
implementation (nothing exciting here)
public class Foo implements MyWrapper {
private String myField;
public Foo() {}
public Foo(String f) {
setMyField(f);
}
#Override
public String getMyField() {
return myField;
}
#Override
public void setMyField(String f) {
myField = f;
}
}
testing :
public static void main(String[] args) {
ObjectMapper mapper = new ObjectMapper();
// serialization - ignore field
try {
MyWrapper w = new Foo("value");
String json = mapper.writeValueAsString(w);
System.out.println("serialized MyWrapper: " + json);
} catch (Exception e) {
e.printStackTrace();
}
// de-serialization - read field
String json = "{\"my_field\":\"value\"}";
try (InputStream is = new ByteArrayInputStream(json.getBytes("UTF-8"))) {
MyWrapper w = (MyWrapper)mapper.readValue(is, Foo.class);
System.out.println("deserialized MyWrapper: input: " + json + " ; w.getMyField(): " + w.getMyField());
} catch (Exception e) {
e.printStackTrace();
}
}
output:
serialized MyWrapper: {}
deserialized MyWrapper: input: {"my_field":"value"} ; w.getMyField(): value
I have JSON request and response, I want to print the JSONs in the log, but there are some secured fields which I want to avoid to print in the log, I am trying to mask fields keys:
example:
before masking:
{"username":"user1","password":"123456","country":"US","creditCardNumber":"1283-1238-0458-3458"}
after masking
{"username":"user1","password":"XXXXXX","country":"US","creditCardNumber":"XXXXXX"}
I am using java Gson lib, please help me to do that
EDIT
I want to pass the keys dynamically, so in function a I want to mask these fields, but in function b different fields.
I think you should exclude that fields from log. Below is a simple example using Gson and #Expose annotation.
public static void main(String[] args) throws IOException {
String json = "{\"username\":\"user1\",\"password\":\"123456\",\"country\":\"US\",\"creditCardNumber\":\"1283-1238-0458-3458\"}";
Gson gson = new Gson();
User user = gson.fromJson(json, User.class);
System.out.println(gson.toJson(user));
Gson gsonExpose = new GsonBuilder().excludeFieldsWithoutExposeAnnotation().create();
System.out.println(gsonExpose.toJson(user));
}
public class User {
#Expose
private String username;
private String password;
#Expose
private String country;
private String creditCardNumber;
}
Output will be:
{"username":"user1","password":"123456","country":"US","creditCardNumber":"1283-1238-0458-3458"}
{"username":"user1","country":"US"}
Another solution using Reflection:
public static void main(String[] args) throws IOException {
String json = "{\"username\":\"user1\",\"password\":\"123456\",\"country\":\"US\",\"creditCardNumber\":\"1283-1238-0458-3458\"}";
Gson gson = new Gson();
User user = gson.fromJson(json, User.class);
List<String> fieldNames = Arrays.asList("password", "creditCardNumber");
System.out.println(mask(user, fieldNames, "XXXXXXX"));
}
public static String mask(Object object, List<String> fieldNames, String mask) {
Field[] fields = object.getClass().getDeclaredFields();
for (int i = 0; i < fields.length; i++) {
if (fieldNames.contains(fields[i].getName())) {
try {
fields[i].setAccessible(true);
if (fields[i].get(object) != null) {
fields[i].set(object, mask);
}
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
Gson gson = new Gson();
return gson.toJson(object);
}
I like the above solution to mask using reflection but wanted to extend same for other field types and saving masked field to unmask again.
Create annotation #MaskedField on top of field.
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.FIELD)
public #interface MaskedField {
}
public <T> Map<String,? super Object> maskObjectFields(T object){
Map<String,? super Object> values = new HashMap<>();
Arrays.stream(object.getClass().getDeclaredFields()).filter(field->null != field.getAnnotation(MaskedField.class)).
forEach(annotatedField->{
try {
if(annotatedField.getType().isAssignableFrom(String.class)) {
annotatedField.setAccessible(true);
values.put(annotatedField.getName(),annotatedField.get(object));
annotatedField.set(object, maskString((String) annotatedField.get(object)));
}
} catch (IllegalAccessException e) {
e.printStackTrace();
}
});
return values;
}
public <T> void unMaskObjectFields(T object,Map values){
Arrays.stream(object.getClass().getDeclaredFields()).filter(field->null != field.getAnnotation(MaskedField.class)).
forEach(annotatedField->{
try {
annotatedField.setAccessible(true);
annotatedField.set(object,values.get(annotatedField.getName()));
} catch (IllegalAccessException e) {
e.printStackTrace();
}
});
}
private String maskString(String value){
if(Objects.isNull(value)) return null;
return null; //TODO: your logic goes here for masking
}