Creating Resource with references using spring data rest - android

I am using spring data rest, I have following entities exposed via spring data rest
DonationRequest
#Data
#Entity
#Table(name="donation_request",schema="public")
public class DonationRequest {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name="donation_request_id")
Integer donationRequestId;
#Column(name="expiry_datetime")
Date expiryDatetime;
#Column(name="blood_group")
String bloodGroup;
#Column(name="no_of_bottles")
String noOfBottles;
#OneToOne
#JoinColumn(name="hospital_id")
Hospital hospital;
#OneToOne
#JoinColumn(name="user_data_id")
UserData requester;
#Column(name="active")
Boolean active;
}
Hospital
#Data
#Entity
#Table(name="hospital",schema="public")
public class Hospital {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name="hospital_id")
Integer hospitalId;
#Column(name="name")
String name;
#Column(name="address")
String address;
#Column(name="loc",columnDefinition = "geometry")
Point loc;
}
Now I have an android client which has the same class definitions as stated above. Hospitals are cached at startup on android client. Now I want to create a donationRequest entity on server. I can do that easily by posting json of donationRequest object to /api/donationRequests. this json contains hospital object also. But the newly created donationRequest and hospital are not linked together.
Following type of json in postman does not create link:
{
"bloodGroup":"AB+",
"hospital":{
"hospitalId":1
}
}
I know that following json does create link:
{
"bloodGroup":"AB+",
"hospital":"/api/hospitals/1"
}
My question is how can I create link using first type of json as that is the natural way to serialize dontaionRequest object from android client? Also I want hospitals to be exposed via /api/hospitals, so removing that rest resource is not an option.

It can be achieved by using a custom HttpMessageConverter and defining a custom content-type which can be anything other than standard (I used application/mjson):
MHttpMessageConverter.java
public class MHttpMessageConverter implements HttpMessageConverter<Object>{
#Override
public boolean canRead(Class<?> aClass, MediaType mediaType) {
if (mediaType.getType().equalsIgnoreCase("application")
&& mediaType.getSubtype().equalsIgnoreCase("mjson"))
return true;
else
return false;
}
#Override
public boolean canWrite(Class<?> aClass, MediaType mediaType) {
return false;
}
#Override
public List<MediaType> getSupportedMediaTypes() {
return new ArrayList<>(Arrays.asList(MediaType.APPLICATION_JSON));
}
#Override
public Object read(Class<?> aClass, HttpInputMessage httpInputMessage) throws IOException, HttpMessageNotReadableException {
ObjectMapper mapper = new ObjectMapper();
Object obj = mapper.readValue(httpInputMessage.getBody(),aClass);
return obj;
}
#Override
public void write(Object o, MediaType mediaType, HttpOutputMessage httpOutputMessage) throws IOException, HttpMessageNotWritableException {
}
}
CustomRestConfiguration.java
#Configuration
public class CustomRestConfiguration extends RepositoryRestConfigurerAdapter {
#Override
public void configureHttpMessageConverters(List<HttpMessageConverter<?>> messageConverters) {
messageConverters.add(new MHttpMessageConverter());
}
}

Spring Data REST is using HATEOAS. To refer to associated resources we have to use links to them:
Create a hospital first
POST /api/hospitals
{
//...
}
response
{
//...
"_links": [
"hostpital": "http://localhost/api/hospitals/1",
//...
]
}
Then get 'hospital' (or 'self') link and add it to the 'donationRequests' payload
POST /api/donationRequests
{
"bloodGroup":"AB+",
"hospital": "http://localhost/api/hospitals/1"
}
Another approach - create first 'donationRequests' without hospital
POST /api/donationRequests
{
//...
}
response
{
//...
"_links": [
"hostpital": "http://localhost/api/donationRequests/1/hospital"
//...
]
}
then PUT hospital to donationRequests/1/hospital using text link to hospital in your payload (pay attention to Content-Type: text/uri-list)
PUT http://localhost/api/donationRequests/1/hospital (Content-Type: text/uri-list)
http://localhost/api/hospitals/1
Info: Repository resources - The association resource
UPDATE
If it's necessary to deal without links to resources we have to make a custom rest controller.

Related

RxJava2 and Retrofit2 : Expected BEGIN_ARRAY but was BEGIN_OBJECT at line 1 column 2 path $

I have been trying out how to work with RxJava2 and Retrofit 2 to use Github API. Trying to access the link :
<https://api.github.com/search/repositories?q=topic:ruby+topic:rails>.I want to display the NAME and NODE_ID. But Have been stuck with the error:
java.lang.IllegalStateException: Expected BEGIN_ARRAY but was BEGIN_OBJECT at line 1 column 2 path $ .
Here is the Client class for Retrofit.
public class Client {
public static final String GITHUB_BASE_URL = "https://api.github.com/search/";
private static Client instance;
private static GetData data;
public Client() {
final Gson gson = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.
LOWER_CASE_WITH_UNDERSCORES).create();
final Retrofit retrofit = new Retrofit.Builder().baseUrl(GITHUB_BASE_URL).
addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.addConverterFactory(GsonConverterFactory.create(gson)).build();
data = retrofit.create(GetData.class);
}
public static Client getInstance(){
if(instance==null)
instance = new Client();
return instance;
}
public Observable<List<itemsClass>> getAllUsers(){
return data.getAllUsers();
}
The interface
#GET("repositories?q=topic:ruby+topic:rails")
Observable<List<itemsClass>> getAllUsers();
The data I need to get through the call are like this:
{
"total_count": 2997,
"incomplete_results": false,
"items": [
{
"id": 8514,
"name": "rails",
"owner": {
"node_id": "MDEyOk9yZ2FuaXphdGlvbjQyMjM="
},
}],
},......
And also the MainActivity from where I will display the data.
subscription = Client.getInstance().getAllUsers().
subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.
mainThread()).subscribe(new Observer<List<ParentClass>>() {
#Override
public void onCompleted() {
Log.d(TAG,"In completed()");
}
#Override
public void onError(Throwable e) {
Log.d(TAG,"In onError()");
Log.d(TAG,""+e);
}
#Override
public void onNext(List<ParentClass> parentClasses) {
Log.d(TAG,"In onNext()");
loadData(parentClasses);
}
});
The issue has nothing to do with RxJava. It's problem with your data model:
Observable<List<itemsClass>> - you are expecting list of entities to come, but in fact your json is entity that has fields total_count, incomplete_results and actual list field items of (apparently) itemsClass. Please use correct model under Observable<...> getAllUsers(); interface method.
The response you get isn't an array.
When using json converters (in your case it's gson), the response must match exactly to the POJO.
You can wrap itemClass with another class like responseClass which will have:
class responseClass {
int total_count;
boolean incomplete_results;
List<itemClass> items;
}
And then, in Client interface:
#GET("repositories?q=topic:ruby+topic:rails")
Observable<responseClass> getAllUsers();
Another option is not to use the auto converter, instead you may do:
#GET("repositories?q=topic:ruby+topic:rails")
Observable<JsonObject> getAllUsers(); //com.google.gson.JsonObject
and then convert only the items array with gson.
Type typeToken = new TypeToken<List<itemClass>>() {}.getType();
List<itemClass> items = new Gson().fromJson(response.get("items"), typeToken);

Check which type of data is coming as response using Retrofit

As I'm using Retrofit, I've designed all the POJOs and it was working flawlessly. API is designed in such a way that it will send the required data if the data is of current date or of future dates but not for past dates. In the response, I'll get a JSON response contains a combination of JSON objects and an array as a value of a JSON object and POJOs are according to that. Now if there is no record for present and future dates then I'll receive a string instead of an array and that leads to API error java.lang.IllegalStateException: Expected BEGIN_ARRAY but was STRING. So what I wanna know if there is any way that I can determine - what I'm receiving an array or a string? and how to update POJO according to that to avoid that error.
JSON response when server has no data
{
"Result_Code": "ND",
"Result_Status": "Success",
"Result_Message": "No record found in database.",
"Result_Output": "",
"updatedDate": "20-07-2017 10:44:37"
}
JOSN response will be same when server has data but with one difference
{
"Result_Code": "ND",
"Result_Status": "Success",
"Result_Message": "record found in database.",
"Result_Output": [{multiple elements},
{multiple elements},
{multiple elements}....],
"updatedDate": "20-07-2017 10:44:37"
}
Pojo class named ResponseModel
public class ResponseModel {
private String Result_Code;
private String Result_Status;
private String Result_Message;
private Object Result_Output;
private String updatedDate;
...
}
using Object you can morph as below
call.enqueue(new Callback<ResponseModel>()
{
#Override
public void onResponse(Response<ResponseModel> response, Retrofit retrofit)
{
parseData(); // get other data from ResponseModel Class
if (response.getResultOutput() instanceof List<POJO>)
{
doSomething();
}
else if (response.getResultOutput() instanceof String)
{
doSomething();
}
else //must be error object
{
doSomething();
}
}
#Override
public void onFailure(Throwable t)
{
///Handle failure
}
});
using instanceof you check desired Object type
Where List<POJO> used for multiple elements Model
*check updated solution for parsing ArrayList from response object
hopefully it might be work as you want
catch your json in debug mode and generate pojo class with link below. then compare your class and see difference
http://www.jsonschema2pojo.org/
you can try this method.
try {
callArrayPojo();
} catch (IllegalStateException e) {
callStringPojo();
} catch (Exception e) {
//other}
or you can get ResultMessage generic type
...
private String Result_Code;
private String Result_Status;
private T Result_Message;
...

Parsing an inconsistent json(different json structure for same API call) using Gson Library

I am working on a Android app, where I have a web service call and that service returns me a Json Object as response but in some cases it is returning a different structure, please find the structure:
Case 1: Json Structure
The Json have keys called "from" and "to" which are of type String.
Ex: "to": "BVRT",
"from": "NS",
Case 2: Json Structure
The Json have keys called "from" and "to" which are of custom object.
ex:
"from": {
"name": "BANGALORE CY JN",
"code": "SBC"
},
"to": {
"name": "BHIMAVARAMTOWN",
"code": "BVRT"
},
As there is lot of nested objects I am using Gson Library to parse the json object which makes life easier instead of manual parsing.
Issue Facing
How do I construct my pojo class since there are two different structures for same api call,I have tried JsonIgnore but it didn't worked as it has the same json key in both the cases.
Please find the screenshot for better understanding which have the complete Json structure, hoping a reply with sample code snippet as this has been a blocker for which we could not proceed to further functionality of the app.Json Structure
After research of few hours I got a solution for this, here my solution works without creating separate model classes to handle different Json structures for same API call:
Model Class
public class TrainDetails {
#SerializedName("to")
private String toString;
#SerializedName("from")
private String fromString;
#SerializedName("to")
private ToStationPnrPojo fromStationObject;
#SerializedName("from")
private ToStationPnrPojo toStationObject;
}
Now construct a Gson object using ExclusionStrategy, here you can specify which fields you need to exclude in our model class in order to match the Json structure sent from server side.
try {
JSONObject jsonObject=new JSONObject(result);
JSONArray jsonArray=jsonObject.optJSONArray("train");
if(jsonArray.length()>0){
JSONObject jsonObject1= jsonArray.getJSONObject(0);
Object test=jsonObject1.get("to");
if(test instanceof String){
Log.e("Test","Instance OF String");
CustomExclusionStrategy ges = new CustomExclusionStrategy(TrainDetails.class, "toStationObject","fromStationObject");
gson= new GsonBuilder().setExclusionStrategies(new CustomExclusionStrategy[]{ges}).create();
}else{
Log.e("Test","Instance OF Custom object");
CustomExclusionStrategy ges = new CustomExclusionStrategy(TrainDetails.class, "toString","fromString");
gson= new GsonBuilder().setExclusionStrategies(new CustomExclusionStrategy[]{ges}).create();
}
}
} catch (JSONException e) {
e.printStackTrace();
}
We can pass the fields names to CustomeExclusionStrategy specifying Gson that exclude these fields while parsing the Json from server.
CustomExclusionStrategy
public class CustomExclusionStrategy implements ExclusionStrategy {
private final List<String> _skipFields = new ArrayList<String>();
private final Class<?> _clazz;
public CustomExclusionStrategy(Class<?> clazz, String... fields) {
_clazz = clazz;
for (String field : fields) {
_skipFields.add(field);
}
}
#Override
public boolean shouldSkipClass(Class<?> clazz) {
return false;
}
#Override
public boolean shouldSkipField(FieldAttributes f) {
return f.getDeclaringClass() == _clazz
&& _skipFields.contains(f.getName());
}
}
Hope this will be helpfull if any other were facing the similar issue while dealing with parsing of Json which is not in the client application control.

Retrofit Error: How would I go about deserializing any kind of response from the server?

I am using retrofit for android has been working great but I was wondering if there is a better solution to my problem.
Background
So I have a backend server which may respond with a server related error message in JSON form (not network) like "could not find ID X" etc.
It looks like this
{
"data": {
"errors": {
"base": [
"You could do the following because of blah blah"
]
}
}
}
So the problem is the JSON error object which contains the array "base" might NOT be called "base" it could be called something else completely different. My backend has many kind of error response message name which cannot be changed so easily.
My question, it is possible to deserializise this JSON array without having to know its name in advance.
So far I have been doing this which is becoming a pain.
public class MyRetrofitError
{
#SerializedName(JsonConstants.ERROR_KEY)
private Errors errors;
public Errors getErrors() {
return errors;
}
public void setErrors(Errors errors) {
this.errors = errors;
}
public static class Errors
{
private String errorMessage;
private ErrorCodes errorCodes;
public enum ErrorCodes{
BOOKED_OVER_20,PROMO_CODE_FAILED;
}
#SerializedName("booked_over")
private ArrayList<String> bookedOver= new ArrayList<String>();
#SerializedName("promo_fail")
private ArrayList<String> promoFailed= new ArrayList<String>();
public String getErrorMessage() {
if(promoFailed!= null && promoFailed.size() > 0) {setErrorMessage(promoFailed().get(0)); errorCodes = ErrorCodes.PROMO_CODE_FAILED;}
if(bookedOver!= null && bookedOver.size() > 0){setErrorMessage(bookedOver().get(0)); errorCodes = ErrorCodes.BOOKED_OVER_20; }
return errorMessage;
}
private void setErrorMessage(String errorMessage) {
this.errorMessage = errorMessage;
}
public ErrorCodes getErrorCodes() {
return errorCodes;
}
}
}
Please note: the root "data" object is not present in this, its handled else where via generics so I omitted it for this question
And then I would use the retrofit Error class getBodyAs method to map the incoming error to this model.
Is their a better way? Please note the backend is beyond my control.
Assuming you are using Gson with Retrofit (it is default). You can write a Custom Gson Deserializer for your errors class and save however you would like.
Here is a quick example to give you the idea (not tested)
// Possible error class
public static class Errors {
Map<String, List<String>> errors;
public void addErrors(String key, List<String> issues) {
errors.put(key, issues);
}
}
class ErrorDeserialzer implements JsonDeserializer<Errors> {
#Override
public Errors deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
throws JsonParseException {
Set<Map.Entry<String, JsonElement>> entries = jsonElement.getAsJsonObject().entrySet();
for (Map.Entry<String, JsonElement> issues : entries) {
List<String> values = new ArrayList<>();
for (JsonElement value : issues.getValue().getAsJsonArray()) {
values.add(value.getAsString());
}
errors.add(issues.getKey(), values);
}
return errors;
}
}
EDIT
I also just found this link which may be of help

Retrofit returns object with null members

When I try to parse the following JSON with Retrofit, I end up with null member objects.
Parsing:
RestAdapter restAdapter = new RestAdapter.Builder()
.setEndpoint(CallerInfo.API_URL)
.setLogLevel(RestAdapter.LogLevel.FULL)
.build();
InGameInfo igi = restAdapter.create(InGameInfo.class);
Game game = igi.fetchInGameInfo("EUW", "sasquatching");
Log.d("Cancantest", "Game " + game); //Not null
Log.d("Cancantest", "Team one " + game.getTeamOne()); //Null
Game Class:
#SerializedName("teamTwo")
#Expose private Team teamTwo;
#SerializedName("teamOne")
#Expose private Team teamOne;
public void setTeamOne(Team teamOne) {
this.teamOne = teamOne;
}
public void setTeamTwo(Team teamTwo) {
this.teamTwo = teamTwo;
}
public Team getTeamOne() {
return teamOne;
}
public Team getTeamTwo() {
return teamTwo;
}
Team Class:
#SerializedName("array")
#Expose private TeamMember[] teamMembers;
public void setTeamMembers(TeamMember[] teamMembers) {
this.teamMembers = teamMembers;
}
public TeamMember[] getTeamMembers() {
return teamMembers;
}
Example JSON:
{
"game":{
"teamTwo":{
"array":[]
},
"teamOne":{
"array":[]
}
}
}
The JSON contains a top level "game" entry so you cannot directly deserialize an instance of game. You need another type which has a field of type Game that represents the response.
public class Response {
public final Game game;
public Response(Game game) {
this.game = game;
}
}
You can put your JSON in a string and use Gson directly to test how the response will be deserialized. This behavior has almost nothing to do with Retrofit and all to do with the behavior of Gson.
String data = "...";
Game game = gson.fromJson(data, Game.class);
Response response = gson.fromJson(data, Response.class);
There can be one more reason for somewhat similar behavior: in this case debugger actually has no field members for the response returned from Retrofit.
And the reason for that is proguard. If you are using minifyEnabled true, make sure you explicitly tell it to keep your POJOs. It can be something like that:
#save model classes
-keep class com.example.app.**.model.** {*; }

Categories

Resources