map json string to enum - java

I have the following class and enums:
import lombok.Data;
// other imports...
#Data
public class MapTest{
private MyFirstEnum myFirstEnum;
private MySecondEnum mySecondEnum;
}
public enum MyFirstEnum{
MY_FIRST_ENUM1,
MY_FIRST_ENUM2
}
public enum MySecondEnum {
MY_SECOND_ENUM1,
MY_SECOND_ENUM2
}
and this spring controller:
#PostMapping("/testMap")
#ResponseBody
public void TestMap(#RequestBody MapTest mapTest){
}
Since an enum can be looked up by its name what I would like to do is to post a json to the controller and that the appropriate props will be serialized by their name:
{
"myFirstEnum": "MY_FIRST_ENUM1",
"mySecondEnum": "MY_SECOND_ENUM2"
}
I've tried to set up a #JsonDeserialize but i couldn't get the type of the enum inside the overridden function:
// what type should i use here?
public static class StringToEnum extends JsonDeserializer<???> {
// how do i get the type of the current enum?
#Override
public ??? deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
??? res = Enum.valueOf(p.getText());
return res;
}
}
Update:
I've failed to mention that i'm using lombok's #data attribute for automatically generating getters and setters, which doesn't work well with enum bindings (not sure why).
I guess that laziness comes with a price.

It should be serialized automatically via jackson but you can force it via #JsonCreator
Redefine your enums as
public enum MyFirstEnum{
MY_FIRST_ENUM1,
MY_FIRST_ENUM2;
#JsonCreator
public static MyFirstEnum fromString(String raw) {
return MyFirstEnum.valueOf(raw.toUpperCase());
}
}
Similarly define your second enum as well in the similar manner.
Imp Note (Mandatory)
MapTest should have public setter / getter for both enums (if declared private, preferred), or declare them public (should be avoided, not preferred)

Related

How to provide converter service to POJO class without passing it as method argument

I have ShoppingList service which is responsible for generating shopping list and a IngredientConverter service which is a helping tool for converting objects. My current implementation looks like this
#Service
#AllArgsConstructor
public class ShoppingListService {
private final RecipeService recipeService;
private final IngredientConverter ingredientConverter;
public ShoppingList generateShoppingList(List<UUID> uuidsOfRecipes) {
List<Recipe> recipes = recipeService.getAllByIDIn(uuidsOfRecipes);
ShoppingList shoppingList = ShoppingList.empty();
for (Recipe recipe : recipes) {
shoppingList.addIngredients(recipe.getIngredients());
}
shoppingList.finishAddition(ingredientConverter);
return shoppingList;
}
}
#RequiredArgsConstructor
public class ShoppingList {
#Getter
private final List<IngredientQuantity> optimizedList;
private final Map<Ingredient, Integer> ingredientAmountMap;
public static ShoppingList empty() {
return new ShoppingList(new ArrayList<>(), new HashMap<>());
}
public void addIngredients(List<IngredientQuantity> ingredients) { ... }
public void addIngredient(IngredientQuantity ingredientQuantity) { ... }
public void finishAddition(IngredientConverter ingredientConverter) {
for (Ingredient ingredient : ingredientAmountMap.keySet()) {
IngredientQuantity ingredientQuantity = ingredientConverter.convertWithAmount(
ingredient.getName(),
ingredientAmountMap.get(ingredient),
ingredient.getUnit());
optimizedList.add(ingredientQuantity);
}
}
}
#Service
public class IngredientConverter {
public IngredientQuantity convertWithAmount(String name, int amount, Unit unit) { ... }
}
Is there a better strategy for providing IngredientConverter service to this class? Could I Autowire it somehow despite ShoppingList being POJO class? Should ShoppingList be marked as Component maybe? Not sure what is the best approach.
You cannot autowire service class into POJO. Autowire can be done only within spring managed classes. I can see that ShoppingList is not a spring managed class. Adding #Component will also not be ideal solution. AFAIK, The best solution to use here would be mapStruct. mapStruct can be used to map fields between entity and POJO. And in cases where any field has to be calculated separately, you can write your custom logic and autowire services. Below are steps
Add mapStruct library to pom.xml
Add below mapper class to your project. componentModel="spring" tells the system that this mapper is managed by spring.
All the fields that have same name will be automapped.
For fields which require conversions, you can write #BeforeMapping
Mapper(componentModel="spring")
public abstract class ShoppingListMapper
{
#Autowired
IngredientConverter ingredientConverter; //autowire method you use.
public abstract shoppingListToShoppingListDTO(ShoppingList shoppingList) throws Exception;
public abstract List<ShoppingList> mapShoppingListsToDTOs(List<ShoppingList> shoppingLists) throws Exception;
#BeforeMapping
public void convertLogic(ShoppingList la, #MappingTarget ShoppingListDTO slDto) throws Exception
{
//your logic to set required shoppinglist field using converter
}
}
If this example is not clear, you can refer to web for various mapstruct examples. Let me know if you need further help

Jackson create object by class name

Is there opportunity to read from json class name and create and object?
Here is what I mean:
I have an interface
public interface Converter {
void process();
}
Next I also have some data class
public class Source {
private String service;
private String path;
private Converter converter;
}
And a class that implements Converter interface
public class DataConverter implements Converter {
public void process() {
//some code here
}
}
Last but not least. This is part of my json:
"source": {
"service": "VIS",
"path": "/",
"converter": "DataConverter"
}
So the idea is while reading Json via Jackson's mapper.readValue create a DataConverter so it will be available from the Data class via getter.
Thanks!
You can achieve this by writing custom serialisers and deserialisers, and then annotating the field in your Source class. To do this you need to implement the Converter interface. The documentation suggests:
NOTE: implementors are strongly encouraged to extend StdConverter instead of directly implementing Converter, since that can help with default implementation of typically boiler-plate code.
So what you want to do is something like this for the custom Serialiser:
public class ConverterSerializer extends StdConverter<Converter, String> {
#Override
public String convert(Converter value) {
if(value instanceof DataConverter) {
return "DataConverter";
} ...
return "";
}
}
And then annotate the value with #JsonSerialize:
#JsonSerialize(using = ConverterSerializer.class)
private Converter converter;
The same applies for deserialising but you would implement an StdConverter<String,Converter> for which the convert method will take a String and return a Converter. You would then annotate the converter field with #JsonDeserialize and reference the converter.

Custom Jackson serializer on specific fields

I'm looking to have multiple jackson deserializers for the same object(s) all based on a custom annotation.
Ideally I'd have a single POJO like:
public class UserInfo {
#Redacted
String ssn;
String name;
}
Under "normal" conditions I want this object to be serialized the default way:
{"ssn":"123-45-6789", "name":"Bob Smith"}
but for logging purposes (for example) I want to redact the SSN so it doesn't get saved in our logs:
{"ssn":"xxx-xx-xxxx", "name":"Bob Smith"}
I've also looked into using #JsonSerialize and come up with:
public class UserInfo {
#JsonSerialize(using = RedactedSerializer.class, as=String.class)
String firstName;
String lastName;
}
The problem with this is that it ALWAYS uses this rule. Can multiple #JsonSerializers be added and only the specified one be used within the runtime code?
I've also seen "views" but ideally I'd like to atleast show that the field was present on the request - even if I dont know the value.
The 100% safe way would be to use different DTO in different requests. But yeah, if you cant do that, use #JsonView and custom serializer, something like:
class Views {
public static class ShowSSN {}
}
private static class MyBean{
#JsonSerialize(using = MyBeanSerializer.class)
#JsonView(Views.ShowSSN.class)
String ssn;
//getter setter constructor
}
private class MyBeanSerializer extends JsonSerializer<String> {
#Override
public void serialize(String value, JsonGenerator gen,
SerializerProvider serializers) throws IOException {
Class<?> jsonView = serializers.getActiveView();
if (jsonView == Views.ShowSSN.class)
gen.writeString(value); // your custom serialization code here
else
gen.writeString("xxx-xx-xxxx");
}
}
And use it like:
public static void main(String[] args) throws JsonProcessingException {
ObjectMapper mapper = new ObjectMapper();
MyBean bean = new MyBean("123-45-6789");
System.out.println(mapper.writerWithView(Views.ShowSSN.class)
.writeValueAsString(bean));
// results in {"ssn":"123-45-6789"}
System.out.println(mapper.writeValueAsString(bean));
// results in {"ssn":"xxx-xx-xxxx"}
}
Also for example in spring it would be really easy to use
#Controller
public class MyController {
#GetMapping("/withView") // results in {"ssn":"123-45-6789"}
#JsonView(Views.ShowSSN.class)
public #ResponseBody MyBean withJsonView() {
return new MyBean("123-45-6789");
}
#GetMapping("/withoutView") // results in {"ssn":"xxx-xx-xxxx"}
public #ResponseBody MyBean withoutJsonView() {
return new MyBean("123-45-6789");
}
}
I think you could achieve that dynamically by coding not annotations,
inside your methods, you can set the proper Serializer and switch between them
(The code depends on your Jackson version)
ObjectMapper mapper = new ObjectMapper();
SimpleModule testModule = new SimpleModule("MyModule", new Version(1, 0, 0, null));
testModule.addSerializer(new RedactedSerializer()); // assuming serializer declares correct class to bind to
mapper.registerModule(testModule);
https://github.com/FasterXML/jackson-docs/wiki/JacksonHowToCustomSerializers

Java ModelMapper Flat Model to Hierarchical Model

I am using www.modelmapper.org and I am working on mapping the same "flat" java DTO into several "hierarchical" DTO.
The "flat" DTO has a number of primitive properties.
The "hierarchical" have a number of complex types that hold a number of primitive types. These DTOs are very similar, but not the same.
"Flat" DTO:
class TransactionRequest
{
String cashierNumber;
Long amount;
Integer currency;
public String getCashierNumber {..
...
}
"Hierarchical DTOs:
class PurchaseRequest
{
POSInfo posInfo;
Amount amount;
...
public PosInfo getPosInfo {..
public Amount getAmount { ..
...
}
class CancelRequest
{
POSInfo posInfo;
Amount amount;
...
public PosInfo getPosInfo {..
public Amount getAmount { ..
...
}
class Amount
{
BigDecimal value;
Integer currency;
public Integer getCurrency{..
...
}
class PosInfo
{
String cashierNumber;
public String getCashierNumber {..
}
TransactionRequest should be mapped into 1) PurchaseRequest and 2) CancelRequest.
One of the problems is, that the amount has to converted from Long (in minor unit) to a BigDecimal (in major unit with decimal digits). I achieved this by writing my own Long to BigDecimal converter. Now I have the problem to define the mappings required in a reusable fashion. What I don't want is to define the mappings for each target type like this:
class PurchaseMap extends PropertyMap<..
protected void configure()
{
using(new LongToBigDecimalConverter(...)).map().getAmount().setValue(source.getAmount());
...
}
class CancelMap extends PropertyMap<..
protected void configure()
{
using(new LongToBigDecimalConverter(...)).map().getAmount().setValue(source.getAmount());
...
}
I would like to have to only one time define the mapping of the Amount DTO (and all other sub type mappings such as PosInfo and many more) and then re-use these mappings. I tried several options:
The first thing I tried was declaring a mapping for TransactionRequest to Amount DTO in my ModelMapper. First, I assumed that simply declaring this mapping would be enough for the mapping mechanism to be also picked up when mapping a TransactionRequest to a PurchaseRequest. That however is not the case.
The second thing I tried does work, but seems to be overly complicated:
I created a PropertyMap for the mapping of TransactionRequest to Amount.
I created a custom converter TransactionRequest to Amount. This converter requires a ModelMapper in its constructor. The ModelMapper I pass to the constructor is the ModelMapper that created the PropertyMap from 1)
I use this converter in the PropertyMaps of PurchaseRequest and CancelRequest
Here is the code:
public class AmountMap extends PropertyMap<TransactionRequest , Amount>
{
....
protected void configure()
{
using(new LongToBigDecimalConverter(...)).map().getAmount().setValue(source.getAmount());
}
...
}
}
public class TransactionRequestToAmountConverter extends AbstractConverter<TransactionRequest, Amount>
{
private final ModelMapper mapper;
public TransactionRequestToAmountConverter(ModelMapper mapper)
{
this.mapper = mapper;
}
public Amount convert(TransactionRequest transactionRequest)
{
return mapper.map(transactionRequest, Amount.class);
}
}
public class PurchaseRequestMap extends PropertyMap<TransactionRequest, PurchaseRequest>
{
private final ModelMapper mapper;
public PurchaseRequestMap(ModelMapper mapper)
{
this.mapper = mapper;
}
protected void configure()
{
using(new TransactionRequestToAmountConverter(mapper)).map(source).setAmount(null);
...
}
}
Is there anybody out there that knows a simpler approach to this?
Try mapping Long to Amount by itself:
modelMapper.createTypeMap(Long.class, Amount.class).setConverter(new Converter<Long, Amount>() {
//... convert Long to Amount
});
That mapping will then be used in your other types and should eliminate the need for the other stuff (if I'm reading your types correctly).

Deserializing non-string map keys with Jackson

I have a a map that looks like this:
public class VerbResult {
#JsonProperty("similarVerbs")
private Map<Verb, List<Verb>> similarVerbs;
}
My verb class looks like this:
public class Verb extends Word {
#JsonCreator
public Verb(#JsonProperty("start") int start, #JsonProperty("length") int length,
#JsonProperty("type") String type, #JsonProperty("value") VerbInfo value) {
super(length, length, type, value);
}
//...
}
I want to serialize and deserialize instances of my VerbResult class, but when I do I get this error: Can not find a (Map) Key deserializer for type [simple type, class my.package.Verb]
I read online that you need to tell Jackson how to deserialize map keys, but I didn't find any information explaining how to go about doing this. The verb class needs to be serialized and deserialzed outside of the map as well, so any solution should preserve this functionality.
Thank you for your help.
After a day of searching, I came across a simpler way of doing it based on this question. The solution was to add the #JsonDeserialize(keyUsing = YourCustomDeserializer.class) annotation to the map. Then implement your custom deserializer by extending KeyDeserializer and override the deserializeKey method. The method will be called with the string key and you can use the string to build the real object, or even fetch an existing one from the database.
So first in the map declaration:
#JsonDeserialize(keyUsing = MyCustomDeserializer.class)
private Map<Verb, List<Verb>> similarVerbs;
Then create the deserializer that will be called with the string key.
public class MyCustomDeserializer extends KeyDeserializer {
#Override
public MyMapKey deserializeKey(String key, DeserializationContext ctxt) throws IOException, JsonProcessingException {
//Use the string key here to return a real map key object
return mapKey;
}
}
Works with Jersey and Jackson 2.x
As mentioned above the trick is that you need a key deserializer (this caught me out as well). In my case a non-String map key was configured on my class but it wasn't in the JSON I was parsing so an extremely simple solution worked for me (simply returning null in the key deserializer).
public class ExampleClassKeyDeserializer extends KeyDeserializer
{
#Override
public Object deserializeKey( final String key,
final DeserializationContext ctxt )
throws IOException, JsonProcessingException
{
return null;
}
}
public class ExampleJacksonModule extends SimpleModule
{
public ExampleJacksonModule()
{
addKeyDeserializer(
ExampleClass.class,
new ExampleClassKeyDeserializer() );
}
}
final ObjectMapper mapper = new ObjectMapper();
mapper.registerModule( new ExampleJacksonModule() );
Building on the answer given here that suggests to implement a Module with a deserializer. The JodaTime Module is an easy to understand full example of a module containing serializers and deserializers.
Please note that the Module feature was introduced in Jackson version 1.7 so you might need to upgrade.
So step by step:
create a module containing a (de)serializer for your class based on the Joda example
register that module with mapper.registerModule(module);
and you'll be all set
Assuming we have a Map property, like the following:
class MyDTO{
#JsonSerialize(keyUsing = MyObjectKeySerializer.class)
#JsonDeserialize(keyUsing = MyObjectKeyDeserilazer.class)
private Map<MyObjectKey , List<?>> map;
}
We serilize the MyObjectKey as a json string, while call objectMapper.writeAsString;
And deserilize from the json string,to MyObjectKey
public class MyObjectKeySerializer extends StdSerializer<MyObjectKey> {
public Serializer() {
super(MyObjectKey.class);
}
#Override
public void serialize(MyObjectKey value, JsonGenerator gen, SerializerProvider provider) throws IOException {
gen.writeFieldName(JsonUtil.toJSONString(value));
}
}
public class MyObjectKeyDeserializer extends KeyDeserializer {
#Override
public Object deserializeKey(String key, DeserializationContext ctxt) throws IOException {
return JsonUtil.toObject(key, MyObjectKey.class);
}
}
After scouring the web, I think I have a decent solution for how to handle POJO-style keys (although, as always, you are best served not using a full object as a map key).
Serializer (registered as a Jackson module, inside of Spring Boot):
#Bean
fun addKeySerializer(): Module =
SimpleModule().addKeySerializer(YourClass::class.java, YourClassSerializer())
class YourClassSerializer() : JsonSerializer<YourClass>() {
override fun serialize(value: DataElement, gen: JsonGenerator, serializers: SerializerProvider) {
gen.writeFieldName(jacksonObjectMapper().writeValueAsString(value))
}
}
(note that, in a standard Java environment, you will have to instantiate your own objectMapper instance here)
Deserializer:
#Bean
fun addKeyDeserializer(): Module =
SimpleModule().addKeyDeserializer(YourClass::class.java, YourClassDeserializer())
class YourClassDeserializer() : KeyDeserializer() {
override fun deserializeKey(key: String, ctxt: DeserializationContext): YourClass? {
return ctxt.parser.readValueAs(YourClass::class.java)
}
}

Categories