So I'm working on building a lil website that's supposed to showcase insects and some info about em, and I've gotten the fetch call and the site itself up and running, no problem. On the back-end I have an API with a Spring Boot that retrieves my InsectObject, which for now just holds a title and description string and that works fine.
Now, excuse me as I try to explain the issue to the best of my abilities.
My problem is that I'm getting a response as follows from my API:
[
{
"id": 1,
"title": "mantis",
"description": "leafy boi"
},
{
"id": 2,
"title": "moth",
"description": "fly boi"
}
]
Wheres I want it to return it as:
{
bugs: [
{
"id": 1,
"title": "mantis",
"description": "leafy boi"
},
{
"id": 2,
"title": "moth",
"description": "fly boi"
}
]
}
Which is how I think a proper api call should look like. But then again, it's the first time I venture into this territory and I've only been following tutorials and documentation, building my own picture along the way.
If it's of any relevance, my rest controller looks like this:
#RestController
public class BugSiteController {
private final InsectRepository repository;
BugSiteController(InsectRepository repository) {
this.repository = repository;
}
// get all bugs from the repo
#CrossOrigin(origins = "http://localhost:3000")
#GetMapping("/bugs")
List<InsectObject> getAll() {
return repository.findAll();
}
}
What am I missing? Is it something in my getAll() method I should change to get the desired result? or should I be able to work with the first result regardless? Should I maybe return something other than a List<>? I tried with ResponseEntity but had the exact same result, just way more verbose.
Thanks in advance.
You could set the value in Model or you could set it in a Map.
Model
class InsectResponse {
#JsonProperty("bug")
private List<InsectObject> insectObject;
// Getter, Setter & Constructor
}
Controller
#GetMapping("/bugs")
public ReponseEntity getAll() {
return ResponseEntity.ok(new InsectResponse(repository.findAll()));
}
or
#GetMapping("/bugs")
public ReponseEntity getAll() {
return ResponseEntity.ok(new HashMap<String, List<InsectObject>>() {{
put("bug", repository.findAll());
}});
}
On the InsectObject entity class add:
#JsonRootName(value = "bug")
Related
I have a simple json like this
{
"someReports":[
{
"reportName": "PR123",
"fields": [
]
},
{
"reportName": "PR234",
"fields": []
}
]
}
I have a class that looks somewhat like this inside which getSomeReports() is defined.
class AHeckLotOfReports {
private String someString;
private List<SomethingElse> some;
..
#JsonProperty("someReports")
private List<SomeReports> someReports;
}
//POJO:
class SomeReport {
String reportName;
List<Field> fields;
...
}
//REST Controller looks like this. some injection code is cleaned up
#Api(tags = “SomeReport”)
#Controller
#ThreadSafe
#RequestMapping(“/report/v2")
public class ReportController{
#ApiOperation(value = "Create a new report.”)
#RequestMapping(value = “/report”, method = RequestMethod.POST)
#ResponseBody
public ReportResponse addReport(
#Nonnull final AuthorizationToken authorizationToken,
#Nonnull#RequestBody final AHeckLotOfReports reportRequest) {
final Report report=this.reportService.addReport(
authorizationToken,
reportRequest.getName(),
reportRequest.isEnabled(),
reportRequest.getConfiguration().or(ReportConfiguration.empty()),
reportRequest.getNewConfiguration(),
reportRequest.getDefinition());
return ReportResponse.fromReport(report);
}
I haven't been able to get this working. I do get the structure intact but reportName comes up blank, the fields array comes up empty.
I have tried JsonAlias("someReports","some_reports") and that seems to make no difference.
Anything comes out as an obvious "duh" in this ?
EDIT: My apologies, I did realize I have not provided the entire context. I AM able to deserialize the simple POJO with ObjectMapper. But the class AHeckLotOfReports is used as request object in REST endpoint for a POST and this is where the problem surfaces
Software: jackson.core 2.9.9, jackson.datatype 2.9.8 and JDK 8.0, Spring-Boot 2.2.6
I'm implementing a company internal REST service using spring boot 1.5.2 with Spring Data JPA and Data Rest.
Problem
I'm looking for an efficient way to serialize objects as strings when exposing certain domain models using Spring Data Rest-Repositories.
Context
My domain models all extend from BaseEntity which looks like this:
#MappedSuperclass
#EntityListeners(AuditingEntityListener.class)
public abstract class BaseEntity extends AbstractAuditable<User, Long> implements Serializable {
#Version
private Long version;
}
With this, each domain model has the properties createdBy, createDate, lastModifiedBy and lastModifiedDate exposed as shown in this example entity:
public class TestEntity extends BaseEntity { private String name; }
The corresponding JSON output looks like this:
{
"createdBy":
{
"name": "testEM",
"contactInfo":
{
"title": null,
"givenName": "GivenName",
"surName": "Surname",
"mail": "test#test.mail.de"
},
"function": "EMPLOYEE",
"department":
{
"name": "mydep"
}
},
"createdDate": "2017-06-12T11:49:17.013Z",
"lastModifiedBy":
{
<same representation as "createdBy">
},
"lastModifiedDate": "2017-06-14T11:27:32.370Z",
"name": "Hello,Name!",
"new": false,
"_links":
{
"self":
{
"href": "http://localhost:8080/testres/1"
},
"testEntity":
{
"href": "http://localhost:8080/testres/1{?projection}",
"templated": true
}
}
}
What I want
Now I'd like to achieve a shorter representation of createdBy and lastModfifiedBy so that these entries don't contain the User object. Instead only the name (from User.getName()) should be displayed:
{
"createdBy": "testEM",
"createdDate": "2017-06-12T11:49:17.013Z",
"lastModifiedBy": "testEM",
"lastModifiedDate": "2017-06-12T11:49:17.013Z",
... // other properties
}
What is the best way to achieve this?
I've tried:
using #JsonIdentityInfo on the User entity -- This one didn't have any effect at all
registering custom (de)serializers for the User entity via #Bean Jackson2ObjectMapperBuilderCustomizer customizer() {...} -- Rendered { "createdBy": { "content": "testEM"}}
annotating the overridden method public User getCreatedBy() in my BaseEntity class with #JsonSerialize(using= UserJsonSerializer.class) -- this one throws an exception
{
"timestamp": 1497515751192,
"status": 500,
"error": "Internal Server Error",
"exception": "org.springframework.http.converter.HttpMessageNotWritableException",
"message": "Could not write content: Can not override serializer; nested exception is com.fasterxml.jackson.databind.JsonMappingException: Can not override serializer",
"path": "/testres/1"
}
I've also read about using #JsonView, however, it didn't become clear to me how to enable these for the given use case
Update
I've created some projections, which is the out-of-the-box supported way to reduce output. See this Gist for the code I've written.
With these in place, and the projections set as excerpts, the list of entries is displayed fine. However, when you request a specific resource like localhost:8080/testRepo/1 you get the unprojected output. I know that Spring won't apply projections to specific entities per default. So we'd have to apply the request parameter ?=projection=testProjection to each request.
Since this is doable (because the app won't be public) it may be okay, but for others it may not. So the questions still stands, how can we alter the audit info in an efficient way for each resource?
Update 2
I've read again the Spring Data REST Documentation and stumbled upon this paragraph:
There is another route. If the Address domain object does not have it’s own repository definition, Spring Data REST will inline the data fields right inside the Person resource.
So you have to expose an UserRepository when the auditor is of type User.
Coincidently, this is the exact behaviour which I experienced when creating a MWE (minimal working example, can't upload to github, since I'm behind a proxy :( ).
So, with a #RepositoryRestResource UserRepository extends JpaRepository<User, Long> publicly exposed, Spring generates this JSON:
{
"createdDate": "2017-06-12T11:49:17.013Z",
"lastModifiedDate": "2017-06-14T11:27:32.370Z",
"name": "Hello,EM!",
"new": false,
"_links":
{
"self":
{
"href": "http://localhost:8080/testRepo/1"
},
"testEntity":
{
"href": "http://localhost:8080/testRepo/1{?projection}",
"templated": true
},
"lastModifiedBy":
{
"href": "http://localhost:8080/testRepo/1/lastModifiedBy"
},
"createdBy":
{
"href": "http://localhost:8080/testRepo/1/createdBy"
}
}
}
This behaviour is acceptable for me, so consider this question solved.
If anyone has additional input feel free to post!
Any help here is much appreciated!
This isn't a solution for my asked question, but it is an acceptable compromise for me and the company.
Quick solution:
When you expose an RestRepository<User> in your API and your auditor is of the same type User, Spring will generate HAL-links to createdBy and lastModifiedBy. Both audit dates will be inlined still since they are simple strings (due to the JodaTime conversion).
Example code:
// resolves auditor from SecurityContext
public class AuditorAwareImpl implements AuditorAware<User> {
#Override
public User getCurrentAuditor() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication != null && authentication.getPrincipal() instanceof WrappedUser) {
WrappedUser principal = (WrappedUser)authentication.getPrincipal();
return principal.getUser();
}
throw new IllegalStateException("No current auditor available!");
}
}
Expose the UserRepository:
//exported is true by default
#RepositoryRestResource(exported = true)
public interface UserRepository extends JpaRepository<User, Long> {
Optional<User> findByName(String loginName);
}
Create AuditEntity from which all other domain objects inherit:
#MappedSuperclass
#EntityListeners(AuditingEntityListener.class)
public abstract class BaseEntity extends AbstractAuditable<User, Long> implements Serializable {
#javax.persistence.Version
private Long version;
}
Expose your domain models:
#Entity
public class Project extends BaseEntity {
private String project_name;
// other properties
}
#RepositoryRestResource
public interface ProjectRepo extends JpaRepository<User, Long> {}
This will generate following JSON for /projects/{id}:
{
"createdDate": "2017-06-12T11:49:17.013Z",
"lastModifiedDate": "2017-06-14T11:27:32.370Z",
"project_name": "MyExampleProjectName",
"new": false,
"_links":
{
"self":
{
"href": "http://localhost:8080/projects/1"
},
"project":
{
"href": "http://localhost:8080/projects/1{?projection}",
"templated": true
},
"lastModifiedBy":
{
"href": "http://localhost:8080/projects/1/lastModifiedBy"
},
"createdBy":
{
"href": "http://localhost:8080/projects/1/createdBy"
}
}
}
I have a class where I have multiple field named results, (Actually I have 12 of them, but for the sake of the question, I just include 2 in this question)
public class APIRequest {
#JsonProperty("code")
public String code;
#JsonProperty("error")
public APIError error;
#JsonProperty("results")
public APILogin login;
#JsonProperty("results")
public APIUser user;
}
The reason I have this because my backend API call will always return the results field for every request
for example http://api.testapp.com/get_user_profile would return this JSON
The results key would then be mapped by APIUser class
{
"code": "200",
"results": {
"name": "Jackson Liu"
"age": "21"
"first_name": "Jackson"
"last_name": "Liu"
}
}
And then http://api.testapp.com/login would return this JSON
The results key would then be mapped by APILogin class
{
"code": "200",
"results": {
"token": "12u3912edsdnisnknaklsmdlsadmsalma"
"session_id": "ladlwjopwjwpdmdlndlkadlaasassa"
"state": "1"
}
}
And because of that, I will get this error.
com.fasterxml.jackson.databind.JsonMappingException: Multiple fields
representing property "results":
id.testapp.android.testapp.jsonobjects.APIResults#login vs
id.testapp.android.testapp.jsonobjects.APIResults#user
Any thoughts on how should I fix this?
To make it simple use MAP. Jackson will take care of populating MAP. Just provide setter and getter for each field. And Depends on your context you can read required field in Map
public class APIRequest {
#JsonProperty("code")
public String code;
#JsonProperty("error")
public APIError error;
#JsonProperty("results")
Map<String, String> results;
}
I'm receiving this error:
com.google.appengine.repackaged.org.codehaus.jackson.map.JsonMappingException: Can not deserialize instance of java.util.ArrayList out of VALUE_STRING token↵ at [Source: N/A; line: -1, column: -1] (through reference chain: com.test.web.TestFM["fields"])
I've mocked up a simplified version of my situation that throws the same error:
Cloud Endpoint:
#Api(name = "testApi", version = "v1", clientIds={Constants.WEB_CLIENT_ID, com.google.api.server.spi.Constant.API_EXPLORER_CLIENT_ID})
public class TestEndpoint {
public void Test(TestFM test){
// nothing necessary here to recreate
}
}
TestFM:
public class TestFM {
public int id;
public List<SubTestFM> fields;
}
SubTestFM:
public class SubTestFM {
public String property1;
public String property2;
}
Javascript:
gapi.client.testApi.testEndpoint.test({
id:7,
fields:[
{ property1: 'test', property2: 'test2' },
{ property1: 'test3', property2: 'test4' }
]
}).execute(function(resp){console.log(resp);});
I had thought this might be a limitation of endpoints until I realized that when submitting the same data through the API Explorer, everything works exactly as expected.
The successful Request produced by API Explorer:
POST http://localhost:8888/_ah/api/testApi/v1/Test
Content-Type: application/json
X-JavaScript-User-Agent: Google APIs Explorer
{
"id": 7,
"fields": [
{
"property1": "test",
"property2": "test2"
},
{
"property1": "test3",
"property2": "test4"
}
]
}
Any help is greatly appreciated!
After reading my question, I realized there was a reasonable probability that the field name "fields" is reserved or otherwise used by one of the libraries involved in Google Cloud Endpoints.
Changing the name from "fields" (in TestFM above) has resolved this error in the test code above and in my own code.
I'm trying to use Jersey and Jackson (although any other way of doing JSON demarshalling works) to get this into my system in some form (be it POJO or some other representation).
Basically I only need the data section. I was trying to use GenericTypes with lists, but this is a nested list and I'm just not sure what to do. Lots of kudos for help and I really appreciate it!
{
"total": 4,
"data": [
{
"descriptor": "",
"multiInstance": false,
"active": false
},
{
"descriptor": "Apparel",
"multiInstance": true,
},
{
"descriptor": "abcd123",
"multiInstance": false,
},
{
"descriptor": "abcd",
"multiInstance": false,
}
]
}
This is the class I'm trying to use. I need a list of the class.
public class customObject {
#JsonProperty(value = "descriptor")
private String descriptor;
#JsonProperty(value = "multiInstance")
private Boolean multiInstance;
//getters and setters
}
Edit:
I'm using it in here.
CustomObjectResponse WDCOResponse =
resource
.type(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON)
.header("Authorization", getToken()).get(WDCOResponse.class);
But it's still not working.
Edit2:
Figured this out! Thanks to everyone. :)
I had to add annotation to tell it to ignore if something wasn't found, some of the JSON I'm getting back was not fully-formed in that not all fields were absolutely neccesary.
If you the object you provided is what you are passing to your controller, then you will need one more wrapper object to contain the list like this:
public class CustomRequest {
#JSonProperty(value = "total");
private Integer total;
#JsonProperty(value = "data")
private List<CustomObject> data;
// getters/setters
}
public class CustomObject {
#JsonProperty(value = "descriptor")
private String descriptor;
#JsonProperty(value = "multiInstance")
private Boolean multiInstance;
// getters/setters
}
Then your controller will just have annotations that show that the RequestBody is the CustomRequest class:
#Controller
public class JSONController {
#RequestMapping(value="sendData")
public #ResponseBody CustomResponse sendData(
#RequestBody CustomRequest request)
{
return null;
}
}
If you are still getting errors, please provide detailed error or problem. Thanks!
You'd use POJO like:
public class Response {
int count;
List<customObject> data;
}
and access the data from there:
for (customObject ob : response.data) {
// process ig
}