DynamoDBTypeConverter not working as expected - java

First, the code that runs correctly:
The content in DynamoDB table is :
"test_field" : "x:x:x"
Entity POJO has:
#DynamoDBTypeConverted(converter = SingleFieldConverter.class)
#DynamoDBAttribute(attributeName = "test_field")
private SingleField singleField;
with appropriate getter and setter methods.
Converter class is:
public class SingleFieldConverter implements DynamoDBTypeConverter<String, SingleField> {
#Override
public String convert(SingleField singleField) {
// removed for clarity
}
#Override
public SingleField unconvert(String s) {
// removed for clarity
}
and it works!
But when I change the DynamoDB item to have:
"test_field" : [
"x:x:x",
"x:x:x"
]
and my POJO object to:
#DynamoDBTypeConverted(converter = SingleFieldConverter.class)
#DynamoDBAttribute(attributeName = "test_field")
private List<SingleField> singleField;
and converter class to be like:
public class SingleFieldConverter implements DynamoDBTypeConverter<String, List<SingleField>> {
#Override
public String convert(List<SingleField> singleField) {
// removed for clarity
}
#Override
public List<SingleField> unconvert(String s) {
// removed for clarity
}
}
it throws:
DynamoDBMappingException
. . . . . [test_field]; could not unconvert attribute
. . .Caused by: com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMappingException: expected S in value {L: [{S: x:x:x:,}, {S: x:x:x:,}],}
Similar examples I found on the web allegedly work, but my example does not. What am I missing, please help?

Based on the error it looks like the converter return a String. To avoid confusion, I suggest to rename the SingleFieldConverter to SingleFieldListConverter.
I confirm that complex type are supported. I use this Generic without any problems:
public abstract class MapToJsonStringConverter<K, V> implements DynamoDBTypeConverter<String, Map<K, V>> {
private final static ObjectMapper mapper= new ObjectMapper();
#Override
public String convert(Map<K, V> map) {
try {
return mapper.writeValueAsString(map);
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
}
}
Each unconvert are usage specific.

Related

No converter found capable of converting from type String to type for EmbeddedId

I'm getting the error below when I try to POST or PUT to my rest resource. The GET and DELETE (for individual Starlinks) requests works just fine FWIW. I have other resources which basically follow the same pattern of classes—some with entities with EmbeddedIds and some without, and all their REST methods work properly. The only difference is that in this instance, I introduced an entity relationship (#ManyToOne) between my StarLink class and a Star class which allowed me to access my StarLinks from a Star resource through HATEOAS—but that seems to have thrown a wrench in things. Tried looking for solutions but, I'm beat.
org.springframework.core.convert.ConverterNotFoundException: No converter found capable of converting from type [java.lang.String] to type [com.beezassistant.configurator.models.StarLinkId]
...
Here are the relevant classes:
StarLinkRepository.java
#RepositoryRestResource(exported=true, path="starlinks")
public interface StarLinkRepository extends JpaRepository<StarLink, StarLinkId> {}
StarLink.java
#Entity
#Table(name="starlink")
public class StarLink implements Serializable {
// ...
#EmbeddedId
private StarLinkId starLinkId;
#ManyToOne
#MapsId("starName")
private Star star;
private String linkName;
public StarLink() {
super();
starLinkId = new StarLinkId();
}
// Getters and setters
}
StarLinkId
#Embeddable
public class StarLinkId implements Serializable {
// ...
private String starName;
private String link;
public StarLinkId() {
super();
}
// Getters and setters
// equals and hashCode
}
StarLinkIdConverter
#Component
public class StarLinkIdConverter implements BackendIdConverter {
#Override
public boolean supports(Class<?> delimiter) {
return StarLink.class.equals(delimiter);
}
#Override
public Serializable fromRequestId(String id, Class<?> entityType) {
String[] parts = id.split("__");
StarLinkId starLinkId = new StarLinkId();
starLinkId.setStarName(parts[0]);
try {
starLinkId.setLink(
URLDecoder.decode(
parts[1],
StandardCharsets.UTF_8.toString()
)
);
}
catch (UnsupportedEncodingException e) {
starLinkId = null;
}
return starLinkId;
}
#Override
public String toRequestId(Serializable id, Class<?> entityType) {
StarLinkId starLinkId = (StarLinkId) id;
try {
return String.format(
"%s__%s",
starLinkId.getStarName(),
URLEncoder.encode(
starLinkId.getLink(),
StandardCharsets.UTF_8.toString()
)
);
}
catch (UnsupportedEncodingException e) {
return null;
}
}
}
I had the same issue and I found a related issue here.
Implementing a BackendIdConverter will not work as it's for Spring WebMVC controllers.
You need to implement a Converter<String, StarLinkId>, as follows:
public class StarLinkIdConverter implements Converter<String, StarLinkId> {
#Override
public StarLinkId convert(String source) {
String[] parts = source.split("__");
StarLinkId starLinkId = new StarLinkId();
starLinkId.setStarName(parts[0]);
try {
starLinkId.setLink(
URLDecoder.decode(
parts[1],
StandardCharsets.UTF_8.toString()
)
);
}
catch (UnsupportedEncodingException e) {
starLinkId = null;
}
return starLinkId;
}
}
Then you have to register this converter in your configuration, in your class where you extend RepositoryRestConfigurer, as follows:
#Override
protected void configureConversionService(ConfigurableConversionService conversionService) {
super.configureConversionService(conversionService);
conversionService.addConverter(new StarLinkIdConverter());
}
This will give you the conversion from the request URI to your StarLinkId, and this will make requests work as long as you assemble the request URI yourself, but to correctly do HATEOAS you still need to make sure you return the correctly formatted URIs, by overriding toString in your StarLinkId class:
#Override
public String toString() {
try {
return String.format(
"%s__%s",
getStarName(),
URLEncoder.encode(
getLink(),
StandardCharsets.UTF_8.toString()
)
);
}
catch (UnsupportedEncodingException e) {
return null;
}
}
I haven't tested the above code, but these steps fixed this issue for me.

How to use Generic bounded param in method

I am trying to write some generic code and facing issue. Here is code
public abstract class AbstractService<D extends IDTO> {
public String ex(D dto) {
return null;
}
}
public class AService extends AbstractService<TestA> {
#Override
public String ex(TestA dto) {
return "a";
}
}
public class BService extends AbstractService<TestB> {
#Override
public String ex(TestB dto) {
return "b";
}
}
class TestA impements IDTO {
}
class TestB impements IDTO {
}
So as you can see, its really simple code, one AbstractService with bounded param that extends IDTO.
Two implementation of service AService and BService which uses their respective DTO.
Not there is another class that need to call ex() method on basis of runtime instance.
here I am facing the problem.
public class TestAll {
public void executeRequest(final IDTO dto){
// serviceMap contains list of all Services here A and B
serviceMap.get(type).ex(dto);
}
}
Problem on line build().
The method build(capture#5-of ? extends IDTO) in the type AbstractService is not applicable for the arguments (IDTO)
Could someone help to fix this issue?
I found the reason why it was giving me the error. It was my mistake as I was trying to build a map with the help of Spring and was using bounded approach.
It was my previous code.
#Autowired
public void setServicesList(List<AbstractService<IDTO>> abstractServices) {
serviceMap = abstractServices.stream().collect(Collectors.toMap(AbstractService::getType, Function.identity()));
}
and I had to remove the bounded approach and now it's working.
public void setServicesList(List<AbstractService> abstractServices) {
serviceMap = abstractServices.stream().collect(Collectors.toMap(AbstractService::getType, Function.identity()));
}
In case you know what type of service holds the Map, you could do following:
// define your service map
private final Map<String, AbstractService<? extends IDTO>> serviceMap = Map.of(
"a", new AService(),
"b", new BService());
// cast `AbstractServise` from the map into required type:
public void executeRequest(final TestA dto){
((AbstractService<TestA>)serviceMap.get("a")).ex(dto);
}
public void executeRequest(final TestB dto){
((AbstractService<TestB>)serviceMap.get("b")).ex(dto);
}

Spring-boot hateoas convert hateoas links to object instead of collection

I am using spring-boot along with Hateoas. One of my API exposes hateoas links as a collection "_links":[ instead if an object "_links":{. I am not sure why it is using array notation instead of an object. Please find the code below. Any help would be appreciated.
public class Book {
private String id;
private BookInfo bookInfo;
}
public class BookInfo extends ResourceSupport{
private String bookUid;
private String bookName;
private String authhorName;
private String bookGenre;
#Override
#JsonProperty("_links")
#JsonInclude(JsonInclude.Include.NON_NULL)
public List<Link> getLinks() {
return super.getLinks();
}
}
#RestController
#RequestMapping(value = "/api/v1/", produces = APP_JSON)
public class BookController {
#GetMapping("getBooks")
public ResponseEntity<Book> getTransactionStatus() {
Book book = bookRepo.getAllBooks();
book.getBookInfo().add(addLinks(book.getId()));
return ResponseEntity.ok().contentType(MediaType.valueOf(APP_JSON)).body(book);
}
public SuperLink getBookInfoLinks(String bookUid) {
return new SuperLink(
linkTo(methodOn(BookController.class).getBook(bookUid))
.withRel("retrieve-book").expand(),APP_JSON);
}
}
public class SuperLink extends Link {
#XmlAttribute
#JsonInclude(JsonInclude.Include.NON_NULL)
private String accepts;
public SuperLink(Link link) {
super(link.getHref(), link.getRel());
}
public SuperLink(Link link, String accepts) {
super(link.getHref(), link.getRel());
this.accepts = accepts;
}
public String getAccepts() {
return accepts;
}
public void setAccepts(String accepts) {
this.accepts = accepts;
}
}
Actual output
{
"id":"bookId",
"BookInfo":{
"bookUid":"bookUid",
"_links":[
{
"rel":"retrieve-book",
"href":"http://localhost/api/v1/book/bookId",
"accepts":"application/json"
}
]
}
}
Expected output
{
"id":"bookId",
"BookInfo":{
"bookUid":"bookUid",
"_links":
{
"retrieve-book": {
"href":"http://localhost/api/v1/book/bookId",
"accepts":"application/json"
}
}
}
}
This is happening because you are using List in you code.
#Override
#JsonProperty("_links")
#JsonInclude(JsonInclude.Include.NON_NULL)
public List<Link> getLinks() {
return super.getLinks();
}
You should use Link object instead of List of Link.
The links should be serialized as a map, not as a list. You either convert it into a map yourself or you can use custom serializer/deseralizer for that. Fortunately Spring already has them:
#Override
#JsonProperty("_links")
#JsonInclude(Include.NON_EMPTY)
#JsonSerialize(using = Jackson2HalModule.HalLinkListSerializer.class)
#JsonDeserialize(using = Jackson2HalModule.HalLinkListDeserializer.class)
public List<Link> getLinks() {
return super.getLinks();
}
--- edit
In order to make it work you will need the halJacksonHttpMessageConverter bean in the list of message-converters. Create a WebMvcConfigurer and add the halJacksonHttpMessageConverter to the converters in the extendMessageConverters method.
#Autowired
private HttpMessageConverter halJacksonHttpMessageConverter;
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(halJacksonHttpMessageConverter);
}
You should add it to the front of the list, or remove the original jacksonHttpMessageConverter from the list.

Jackson: Serialize comma separated string to json array

Currently I have form like below:
public class Form {
private String listOfItems;
public String getListOfItems() {
return listOfItems;
}
public void setListOfItems(String listOfItems) {
this.listOfItems= listOfItems;
}
}
For instanse listOfItems equals to the following string "1,2,3".
The goal is to serialize this form to following format:
{
"listOfItems": [1, 2, 3]
}
It would be good to know how to correctly do such thing? As I know it is possible to create some custom serializer then mark appropriate getter method with it, like this #JsonSerialize(using = SomeCustomSerializer).
But not sure whether it is correct approach, probably any default implementations already exist.
If you can edit your Form class:
public class Form {
private String listOfItems;
public String getListOfItems() {
return listOfItems;
}
public void setListOfItems(String listOfItems) {
this.listOfItems = listOfItems;
}
#JsonProperty("listOfItems")
public List<Integer> getArrayListOfItems() {
if (listOfItems != null) {
List<Integer> items = new ArrayList();
for (String s : listOfItems.split(",")) {
items.add(Integer.parseInt(s)); // May throw NumberFormatException
}
return items;
}
return null;
}
}
By default Jackson looks for getters for serializing. You can override this by using #JsonProperty annotation.
ObjectMapper mapper = new ObjectMapper();
Form form = new Form();
form.setListOfItems("1,2,3");
System.out.print(mapper.writeValueAsString(form));
Outputs:
{"listOfItems":[1,2,3]}

Custom Xstream/JSON converter for enum

I have the following Enum:
public enum MyState {
Open("opened"),
Close("closed"),
Indeterminate("unknown");
private String desc;
private MyState(String desc) {
setDesc(desc);
}
public String getDesc() {
return this.desc;
}
private void setDesc(String desc) {
this.desc = desc;
}
}
I am trying to write an XStream Converter that will know to map back a JSON element "mystate" to a MyState instance.
"someJson": {
"object1": {
"mystate": closed
}
}
This should produce, amongst other objects (someJson and object1) a MyState.Close instance. I've started the Converter, but haven't gotten very far:
public class MyStateEnumConverter implement Converter {
#Override
public boolean canConvert(Class clazz) {
return clazz.equals(MyState.class);
}
#Override
public void marshal(Object value, HierarchialStreamWriter writer, MarshallingContext context) {
??? - no clue here
}
#Override
public Object unmarshal(HierarchialStreamReader reader, UnmarshallingContext context) {
??? - no clue here
}
}
Then, to create the mapper and use it:
XStream mapper = new XStream(new JettisonMappedXmlDriver());
mapper.registerConverter(new MyStateEnumConverter);
SomeJson jsonObj = mapper.fromXML(jsonString);
// Should print "closed"
System.out.println(jsonObject.getObject1().getMyState().getDesc());
How can I implement marshal and unmarshal so thatI get the desired mapping? Thanks in advance!
You can accomplish this by doing 2 things:
Adding a lookup method as well as a toString() override to your enum (MyStateEnum); and
Extending XStream's AbstractSingleValueConverter instead of implementing Converter
MyStateEnum:
public enum MyStateEnum {
// Everything you had is fine
// But now, add:
public static MyStateEnum getMyStateByDesc(String desc) {
for(MyStateEnum myState : MyStateEnum.values())
if(myState.getDesc().equals(desc))
return myState;
return null;
}
#Override
public String toString() {
return getDesc();
}
}
MyStateEnumConverter:
public class MyStateEnumConverter extends AbstractSingleValueConverter {
#Override
public boolean canConvert(Class clazz) {
return clazz.equals(MyStateEnum.class);
}
#Override
public Object fromString(String parsedText) {
return MyStateEnum.getMyStateByDesc(parsedText);
}
}
By adding getMyStateByDesc(String) to your enum, you now have a way to look up all the various enumerated values from the outside, by providing a desc string. The MyStateEnumConverter (which extends AbstractSingleValueConverter) uses your toString() override under the hood to associate aMyStateEnum instance with a text string.
So when XStream is parsing the JSON, it sees a JSON object of, say, "opened", and this new converter knows to pass "opened" into the converter's fromString(String) method, which in turn uses getMyStateByDesc(String) to lookup the appropriate enum instance.
Don't forget to register your converter with your XStream instance as you already showed in your original question.
You can use the EnumToStringConverter
Documentation
Example
#XStreamConverter(EnumToStringConverter.class)
public enum MyStateEnum {
enter code here
...
Use xstream.autodetectAnnotations(true)
Why are you using xstream for json support? You have a couple of other libraries specialized in json and that do it well. Also closed without quotes is not valid json.
Try for example Genson, it will work out of the box.
The values in the json stream would be "Close", "Indeterminate", etc and when deserializing it will produce the correct enum.
class SomeObject {
private MyState state;
...
}
Genson genson = new Genson();
// json = {"state" : "Indeterminate"}
String json = genson.serialize(new SomeObject(MyState.Indeterminate));
// deserialize back
SomeObject someObject = genson.deserialize(json, SomeObject.class);
// will print unknown
System.out.println(someObject.getDesc());

Categories

Resources