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

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.

Related

Retrofit returning null object

I have a JSON object that looks like this
{
id:int,
tags: [
"string",
"string"
],
images: {
waveform_l:"url_to_image",
waveform_m:"url_to_image",
spectral_m:"url_to_image",
spectral_l:"url_to_image"
}
}
I'm trying to use retrofit to parse the JSON and create the interface. The problem that I have is that I get a null for the images urls. Everything else works, I am able to retrieve the id, the tags, but when I try to get the images they are all null.
I have a sound pojo that looks like this:
public class Sound {
private Integer id;
private List<String> tags = new ArrayList<String>();
private Images images;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public Images getImages() {
return images;
}
public void setImages(Images images) {
this.images = images;
}
... setters and getter for tags as well
}
and I have a Images pojo that looks like this:
public class Images {
private String waveformL;
private String waveformM;
private String spectralM;
private String spectralL;
public String getWaveformL() {
return waveformL;
}
public void setWaveformL(String waveformL) {
this.waveformL = waveformL;
}
public String getWaveformM() {
return waveformM;
}
public void setWaveformM(String waveformM) {
this.waveformM = waveformM;
}
public String getSpectralM() {
return spectralM;
}
public void setSpectralM(String spectralM) {
this.spectralM = spectralM;
}
public String getSpectralL() {
return spectralL;
}
public void setSpectralL(String spectralL) {
this.spectralL = spectralL;
}
}
Whenever I try to call images.getWaveformM() it gives me a null pointer. Any ideas?
#SerializedName can also be used to solve this. It allows you to match the expected JSON format without having to declare your Class variable exactly the same way.
public class Images {
#SerializedName("waveform_l")
private String waveformL;
#SerializedName("waveform_m")
private String waveformM;
#SerializedName("spectral_m")
private String spectralM;
#SerializedName("spectral_l")
private String spectralL;
...
}
If the only differences from the JSON to your class variables are the snake/camel case then perhaps #njzk2 answer works better but in cases where there's more differences outside those bounds then #SerializeName can be your friend.
You possibly need this part:
Gson gson = new GsonBuilder()
.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
.create();
setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES) will allow gson to automatically transform the snake case into camel case.
public class Images {
private String waveform_l;
private String waveform_m;
private String spectral_m;
private String spectral_m;
}
Key name should be same in model as in json other wise it won't recognise it else you haven't define it at GsonBuilder creation.Generate the getter setter for the same and you will be good to go

How do I deserialize this JSON type

I have a JSON that looks like this:
{
"results": {
"exchange": [
"site.com",
{
"currency": "usd",
"last_traded": "2015.24"
}
]
}
}
How do I get the last_traded value?
I wrote some POJO for this, but I can't seem to find a way to get the key-value inside exchange array.
public class ExchangeContainer {
#Expose
private Results results;
public Results getResults() {
return results;
}
public void setResults(Results results) {
this.results = results;
}
#Override
public String toString() {
return ToStringBuilder.reflectionToString(this);
}
}
public class Results {
#Expose
private List<String> exchange = new ArrayList<String>();
public List<String> getExchange() {
return exchange;
}
public void setExchange(List<String> exchange) {
this.exchange = exchange;
}
#Override
public String toString() {
return ToStringBuilder.reflectionToString(this);
}
}
I'm using GSON to deserialize this JSON.
So in order for me to traverse through the model is:
ExchangeContainer response;
String rate = response.getResults().getExchange().get(1); // how to continue?
and I'm stuck.
Implement interface using implements JsonDeserializer in your class
and override deserialize method.
Example link - http://www.javacreed.com/gson-deserialiser-example/
As you have created list of Strings by
private List<String> exchange = new ArrayList<String>();
& setting another list in created list i.e., in exchange
public void setExchange(List<String> exchange)
When you get value by invoking line
String rate = response.getResults().getExchange().get(1);
it contains "last_traded": "2015.24" is it right ?
Now to get 2015.24, you have following choices :-
String[] split(":")
String substring(int beginIndex)
I hope this will solve your problem.

jackson: ignore getter, but not with #JsonView

I'm looking for possibility to serialize transient information only in some cases:
#JsonInclude(Include.NON_NULL)
#Entity
public class User {
public static interface AdminView {}
... id, email and others ...
#Transient
private transient Details details;
#JsonIgnore // Goal: ignore all the time, except next line
#JsonView(AdminView.class) // Goal: don't ignore in AdminView
public Details getDetails() {
if (details == null) {
details = ... compute Details ...
}
return details;
}
}
public class UserDetailsAction {
private static final ObjectWriter writer = new ObjectMapper();
private static final ObjectWriter writerAdmin = writer
.writerWithView(User.AdminView.class);
public String getUserAsJson(User user) {
return writer.writeValueAsString(user);
}
public String getUserAsJsonForAdmin(User user) {
return writerAdmin.writeValueAsString(user);
}
}
If I call getUserAsJson I expected to see id, email and other fields, but not details. This works fine. But I see same for getUserAsJsonForAdmin, also without detail. If I remove #JsonIgnore annotation - I do see details in both calls.
What do I wrong and is there good way to go? Thanks!
You may find the use of the dynamic Jackson filtering slightly more elegant for your use case. Here is an example of the filtering of POJO fields based on a custom annotation sharing one object mapper instance:
public class JacksonFilter {
static private boolean shouldIncludeAllFields;
#Retention(RetentionPolicy.RUNTIME)
public static #interface Admin {}
#JsonFilter("admin-filter")
public static class User {
public final String email;
#Admin
public final String details;
public User(String email, String details) {
this.email = email;
this.details = details;
}
}
public static class AdminPropertyFilter extends SimpleBeanPropertyFilter {
#Override
protected boolean include(BeanPropertyWriter writer) {
// deprecated since 2.3
return true;
}
#Override
protected boolean include(PropertyWriter writer) {
if (writer instanceof BeanPropertyWriter) {
return shouldIncludeAllFields || ((BeanPropertyWriter) writer).getAnnotation(Admin.class) == null;
}
return true;
}
}
public static void main(String[] args) throws JsonProcessingException {
User user = new User("email", "secret");
ObjectMapper mapper = new ObjectMapper();
mapper.setFilters(new SimpleFilterProvider().addFilter("admin-filter", new AdminPropertyFilter()));
System.out.println(mapper.writerWithDefaultPrettyPrinter().writeValueAsString(user));
shouldIncludeAllFields = true;
System.out.println(mapper.writerWithDefaultPrettyPrinter().writeValueAsString(user));
}
}
Output:
{
"email" : "email"
}
{
"email" : "email",
"details" : "secret"
}
It's look like jackson have horrible concept on very cool feature like #JsonView. The only way I discover to solve my problem is:
#JsonInclude(Include.NON_NULL)
#Entity
public class User {
public static interface BasicView {}
public static interface AdminView {}
... id and others ...
#JsonView({BasicView.class, AdminView.class}) // And this for EVERY field
#Column
private String email;
#Transient
private transient Details details;
#JsonView(AdminView.class)
public Details getDetails() {
if (details == null) {
details = ... compute Details ...
}
return details;
}
}
public class UserDetailsAction {
private static final ObjectWriter writer = new ObjectMapper()
.disable(MapperFeature.DEFAULT_VIEW_INCLUSION)
.writerWithView(User.BasicView.class);
private static final ObjectWriter writerAdmin = new ObjectMapper()
.disable(MapperFeature.DEFAULT_VIEW_INCLUSION)
.writerWithView(User.AdminView.class);
public String getUserAsJson(User user) {
return writer.writeValueAsString(user);
}
public String getUserAsJsonForAdmin(User user) {
return writerAdmin.writeValueAsString(user);
}
}
Maybe it's help some one. But I hope to find better solution and because doesn't accept my own answer.
EDIT: because interface can extends (multiple) interfaces, I can use:
public static interface AdminView extends BasicView {}
and just
#JsonView(BasicView.class)
instead of
#JsonView({BasicView.class, AdminView.class})

Convert JSON many objects to single JSON using Jackson

I have JSON, with differents levels field, so I want to convert to a single JSON with fields with one level for example:
{
"prop1":"value1",
"prob2":"value2",
"prop3": {
"prop4":"value4",
"prop5":"value5"
}
... many level fields
}
result
{
"prop1":"value1",
"prop2":"value2",
"prop4":"value4",
"prop5":"value5"
.......
}
I'm using Jackson with annotation #JsonProperty("field"), I haven't problem wih fields of first level , but I donĀ“t know how to access field where to into more inside JSON , for this example are prop4 and prop5.
JsonUnwrapped is the annotation to use, it even works for multi-level nesting. For example:
#RunWith(JUnit4.class)
public class Sample {
#Test
public void testName() throws Exception {
SampleClass sample = new SampleClass("value1", "value2", new SubClass("value4", "value5", new SubSubClass("value7")));
new ObjectMapper().writeValue(System.out, sample);
}
#JsonAutoDetect(fieldVisibility=Visibility.ANY)
public static class SampleClass {
private String prop1;
private String prop2;
#JsonUnwrapped
private SubClass prop3;
public SampleClass(String prop1, String prop2, SubClass prop3) {
this.prop1 = prop1;
this.prop2 = prop2;
this.prop3 = prop3;
}
}
#JsonAutoDetect(fieldVisibility=Visibility.ANY)
public static class SubClass {
private String prop4;
private String prop5;
#JsonUnwrapped
private SubSubClass prop6;
public SubClass(String prop4, String prop5, SubSubClass prop6) {
this.prop4 = prop4;
this.prop5 = prop5;
this.prop6 = prop6;
}
}
#JsonAutoDetect(fieldVisibility=Visibility.ANY)
public static class SubSubClass{
private String prop7;
public SubSubClass(String prop7) {
this.prop7 = prop7;
}
}
}
will generate
{"prop1":"value1","prop2":"value2","prop4":"value4","prop5":"value5","prop7":"value7"}
Try implementing the #JsonUnwrapped annotation. More information at http://jackson.codehaus.org/1.9.9/javadoc/org/codehaus/jackson/annotate/JsonUnwrapped.html

GSON: De-serialising a generic object

I am attempting to de-serialise some JSON from Wikipedia using the GSON library. Typically, I would create each object in Java as they are in JSON, as example:
{
query: {
pages: {
page: {
pageid: 168079,
ns: 0,
title: "2010 FIFA World Cup"
}
}
}
}
This would allow me to de-serialise using the following Java code:
class PageIdResponseGson
{
private QueryResults query;
public static class QueryResults
{
private PageResults pages;
public PageResults getPages() {
return pages;
}
}
public static class PageResults
{
private PageResult page;
public PageResult getPage() {
return page;
}
}
public static class PageResult
{
private int pageid;
private int ns;
private String title;
public int getNs() {
return ns;
}
public int getPageid() {
return pageid;
}
public String getTitle() {
return title;
}
}
public QueryResults getQuery() {
return query;
}
}
However the Wikipedia JSON uses the page-id as the object name, which means I can't take this approach.
{
query: {
pages: {
168079: {
pageid: 168079,
ns: 0,
title: "2010 FIFA World Cup"
}
}
}
}
Is there a way I can use some kind of generic wrapper to handle these dynamic object names?
You have several alternatives here:
You use Hashmaps-->Anything can be a key in a hashmap and GSON will do just fine.
You implement your own TypeAdapter and TypeAdapterFactory to handle such cases and when you meet a field that is a number that does not exists in your target class then you know you have your pageId and it should be remapped on your PageResults.page member.

Categories

Resources