How parse JSON and set as Object Model - retrofit2

I have an API with 2 different response:
response OK
{ "name": "test" }
response KO
[
{
"name_1": "test",
"name_2": "test"
}
]
the problem is that using Retrofit, normally, I use a model to parse results but response KO has not an array name.
How can I create a model? (I cannot change the API)

So to add another POJO you can do this:
private static Gson gson = new GsonBuilder()
.registerTypeAdapter(Model1.class, new GsonDeserializer<Model1>())
.registerTypeAdapter(Model2.class, new GsonDeserializer<Model2>())
//or create a POJO for the names array of Model2
.registerTypeAdapter(Model2.class, new GsonDeserializer<Names>())
Where GsonDeserializer is a custom serializer that can be defined, like:
public class GsonDeserializer<T> implements JsonDeserializer<T> {
#Override
public T deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
JsonObject el = json.getAsJsonObject();
return new Gson().fromJson(el, typeOfT);
}
And then in your Retrofit client you just add:
retrofit = new Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create(gson))
.build();

Related

OpenWeather api returing null values

I am trying to fetch data from openweather api using retrofit2 and Gson but it always return the null value error.
I tried to debug and the response returned is fine yet it returns null.
Here is my code..
POJO CLASSES was taken from here.
http://samples.openweathermap.org/data/2.5/weather?lat=35&lon=139&appid=b6907d289e10d714a6e88b30761fae22
How I am trying to fetch.
API interface
public interface Api {
String api_key = "b6907d289e10d714a6e88b30761fae22";
String BASE_URL= "http://api.openweathermap.org/data/2.5/";
#GET("weather")
Call<Weather> getWeather(#Query("lat") String latitude, #Query("lon") String longitude, #Query("appid") String api_key);
}
LoadWeather()
public void LoadWeather()
{
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(Api.BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build();
Api api = retrofit.create(Api.class);
Call<Weather> weatherCall = api.getWeather("12.9716", "77.5946", api_key);
weatherCall.enqueue(new Callback<Weather>() {
#Override
public void onResponse(Call<Weather> call, Response<Weather> response) {
Weather weather = response.body();
String temp = weather.getList().get(0).getMain().getTemp().toString();
Log.d(TAG,"onReponse :"+weather);
}
#Override
public void onFailure(Call<Weather> call, Throwable t) {
}
});
}
You are using the App Key 'b6907d289e10d714a6e88b30761fae22' which is used for
the examples (samples.openweathermap.org).
You have to subscribe to get your own API key.
Is it possible you forgot to include which API you want to use in the URL?
Maybe you need:
String BASE_URL= "http://api.openweathermap.org/data/2.5/weather?";

How to handle multiple possible response types with Retrofit2, Gson and Rx

The API i have to use sucks, and always returns HTTP 200. But sometimes there is proper response:
[{"blah": "blah"}, {"blah": "blah"}]
and sometimes, there is error:
{"error": "Something went wrong", "code": 123}
I'm using Retrofit2 with Gson converter and Rx adapter:
final Api api = new Retrofit.Builder()
.baseUrl(URL)
.client(client)
.addCallAdapterFactory(RxJavaCallAdapterFactory.createWithScheduler(Schedulers.io()))
.addConverterFactory(GsonConverterFactory.create())
.build()
.create(Api.class);
And now, when I receive error response, the onError handler is called with following exception:
java.lang.IllegalStateException: Expected BEGIN_ARRAY but was BEGIN_OBJECT at line 1 column 2 path $
at com.google.gson.stream.JsonReader.beginArray(JsonReader.java:350)
at com.google.gson.internal.bind.CollectionTypeAdapterFactory$Adapter.read(CollectionTypeAdapterFactory.java:80)
at com.google.gson.internal.bind.CollectionTypeAdapterFactory$Adapter.read(CollectionTypeAdapterFactory.java:61)
at retrofit2.converter.gson.GsonResponseBodyConverter.convert(GsonResponseBodyConverter.java:37)
at retrofit2.converter.gson.GsonResponseBodyConverter.convert(GsonResponseBodyConverter.java:25)
at retrofit2.ServiceMethod.toResponse(ServiceMethod.java:117)
at retrofit2.OkHttpCall.parseResponse(OkHttpCall.java:211)
at retrofit2.OkHttpCall.execute(OkHttpCall.java:174)
at retrofit2.adapter.rxjava.RxJavaCallAdapterFactory$RequestArbiter.request(RxJavaCallAdapterFactory.java:171)
at rx.internal.operators.OperatorSubscribeOn$1$1$1.request(OperatorSubscribeOn.java:80)
at rx.Subscriber.setProducer(Subscriber.java:211)
at rx.internal.operators.OperatorSubscribeOn$1$1.setProducer(OperatorSubscribeOn.java:76)
at retrofit2.adapter.rxjava.RxJavaCallAdapterFactory$CallOnSubscribe.call(RxJavaCallAdapterFactory.java:152)
at retrofit2.adapter.rxjava.RxJavaCallAdapterFactory$CallOnSubscribe.call(RxJavaCallAdapterFactory.java:138)
at rx.Observable.unsafeSubscribe(Observable.java:10144)
at rx.internal.operators.OperatorSubscribeOn$1.call(OperatorSubscribeOn.java:94)
at rx.internal.schedulers.CachedThreadScheduler$EventLoopWorker$1.call(CachedThreadScheduler.java:230)
at rx.internal.schedulers.ScheduledAction.run(ScheduledAction.java:55)
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:428)
at java.util.concurrent.FutureTask.run(FutureTask.java:237)
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:272)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1133)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:607)
at java.lang.Thread.run(Thread.java:761)
How can I solve it? If I could get the response in the onError handler, I could reparse it with proper error model class. But it seems I can't get the raw response.
You can use a custom Gson deserializer to marshal both responses into a single object type. Here is a rough sketch of the idea assuming your current response type is List<Map<String, String>>, you will need to adjust based on your actual return type. I am also making the assumption that the API always returns an array on success --
public class MyResponse {
String error;
Integer code;
List<Map<String, String>> response;
}
interface MyApi {
#GET("/")
Observable<MyResponse> myCall();
}
private class MyResponseDeserializer implements JsonDeserializer<MyResponse> {
public MyResponse deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
throws JsonParseException {
MyResponse response = new MyResponse();
if (json.isJsonArray()) {
// It is an array, parse the data
Type responseType = new TypeToken<List<Map<String, String>>>(){}.getType();
response.response = context.deserialize(json, responseType);
} else {
// Not an array, parse out the error info
JsonObject object = json.getAsJsonObject();
response.code = object.getAsJsonPrimitive("code").getAsInt();
response.error = object.getAsJsonPrimitive("error").getAsString();
}
return response;
}
}
Use the above to create a custom Gson
Gson gson = new GsonBuilder()
.registerTypeAdapter(MyResponse.class, new MyResponseDeserializer())
.create();
use that in your retrofit builder --
.addConverterFactory(GsonConverterFactory.create(gson))
You should also update your interface to return Observable<MyResponse>. You will get both success and error in onNext now. You'll need to inspect the object to determine if it is a successful response (response != null) or not.

Mockito : Mock groovy rest client call

I am using Mockito with Groovy to unit test some rest calls using the Groovy rest client.
How can I mock the bpmApiRestClient.get call in the updatePhase method below?
#Service
public class PhasesBPMServiceImpl implements PhasesBPMService {
Logger logger = Logger.getLogger(PhasesBPMServiceImpl.class)
#Autowired
ObjectMapper objectMapper
#Autowired
BPMConfig bpmConfig
#Autowired
JsonSlurper jsonSlurper
#Autowired
GetMaintenanceActivitiesPhasesCurrentResponseTransformer getMaintenanceActivitiesPhasesCurrentResponseTransformer
#Autowired
PutMaintenanceActivityPhaseRequestTransformer putMaintenanceActivityPhaseRequestTransformer
public void updatePhase(
String loggedInUsernameEncoded,
String phaseDefinitionKey,
String activityId,
Reader reader) {
def bpmApiRestClient = new BpmRestClient(bpmConfig)
try {
def processInstancePhases = bpmApiRestClient.get path: 'task', query: [ processInstanceId: activityId ]
} catch (Exception e) {
e.printStackTrace()
logger.error "Error occurred while updating phase details. Error Message [${e?.message}]. Error Cause [${e?.cause}]"
throw e
}
finally {
bpmApiRestClient.shutdown()
}
}
At one stage I had things working as per the unit test below but once I refactored bpmApiRestClient to be instantiated inside the method rather than at the class level the mocking stopped working.
class PhasesBPMServiceImplPutMaintenanceActivitiesByActivityIdPhasesNameTest {
def restClient, responseTransformer, objectMapper, jsonSlurper, bpmConfig
void init() {
restClient = Mockito.mock(RESTClient)
when(restClient.get(anyObject())).thenReturn([data: [
[ // get current task for process id
id: "2",
name: "Waiting to be allocated",
assignee: "Cosmo Kramer",
created: "2016-11-16T15:10:29"
]
]])
responseTransformer = new PutMaintenanceActivityPhaseRequestTransformer(responseBaseUrl: '/maintenance/activities')
objectMapper = new ObjectMapperConfig().getObjectMapper()
jsonSlurper = new JsonSlurper()
bpmConfig = new BPMConfig(maintenanceProcessName:'Maintenance_Activity_Process',
baseUrl:'http://localhost:12378/v1/camunda/rest/')
}
#Test
void testUpdatePhaseSuccess() {
// setup
init()
PhasesBPMServiceImpl service =
new PhasesBPMServiceImpl(bpmConfig: bpmConfig,
putMaintenanceActivityPhaseRequestTransformer: responseTransformer,
objectMapper: objectMapper,
jsonSlurper: new JsonSlurper())
// invoke
try {
def request = getClass().getResourceAsStream('/in/putPhaseRequest.json').text
def response = service.updatePhase('rriviere', 'Unallocated', '7', new StringReader(request))
} catch (Exception e) {
e.printStackTrace()
fail()
}
}
}
thanks
You refactored your code to be untestable as the bpmApiRestClient cannot be mocked. Make it a dependency of this class (instead of bpmConfig) or refactor retrieving the results into a separate class.

Spring integeration DefaultSoapHeaderMapper - getStandardRequestHeaderNames - override

in previous si versions (si 2.11 to be specific and spring 3.1.1) getStandardRequestHeaderNames could be overrided to include Additional Application specific objects in the si message header. Our application relied on this ability (may be wrongfully so) to override this method and supply a custom POJO to be carried downstream consisting of many splitters, aggregators etc. The app used an ws inbound gateway and used the header-mapper attribute to specify the custom soap header mapper.
Any clues on the reasoning behind why getStandardRequestHeaderNames cannot be overriden?
Need some advise on how I can migrate this to the current spring release.
The requirement is to extract elements from soapHeader and map them to an SI message headers as an POJO and send it down stream.
All help appreciated.
Code Snippet: Works with older versions of spring
<int-ws:inbound-gateway id="webservice-inbound-gateway"
request-channel="input-request-channel"
reply-channel="output-response-channel"
header-mapper="CustomSoapHeaderMapper"
marshaller="marshaller"
unmarshaller="marshaller" />
#Component("CustomSoapHeaderMapper")
public class CustomSoapHeaderMapper extends DefaultSoapHeaderMapper {
private static final Logger logger = Logger.getLogger("CustomSoapHeaderMapper");
public static final String HEADER_SEARCH_METADATA = SearchMetadata.HEADER_ATTRIBUTE_NAME;
public static final String HEADER_SERVICE_AUDIT = "XXXXXXXX";
// Use simulation if security token is set to this value
public static final String SECURITY_TOKEN_SIMULATION = "XXXX";
private static final List<String> CUSTOM_HEADER_NAMES = new ArrayList<String>();
static {
CUSTOM_HEADER_NAMES.add(WebServiceHeaders.SOAP_ACTION);
CUSTOM_HEADER_NAMES.add(HEADER_SEARCH_METADATA);
}
private int version =SearchMetadata.VERSION_CURRENT;
public void setVersion(int version) {
this.version = version;
}
#Override
protected List<String> getStandardRequestHeaderNames() {
return CUSTOM_HEADER_NAMES;
}
#Override
protected Map<String, Object> extractUserDefinedHeaders(SoapMessage source) {
// logger.log(Level.INFO,"extractUserDefinedHeaders");
// call base class to extract header
Map<String, Object> map = super.extractUserDefinedHeaders(source);
Document doc = source.getDocument();
SearchMetadata searchMetadata = new SearchMetadata();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try {
source.writeTo(baos);
baos.flush();
searchMetadata.setRequestXML(baos.toString());
baos.close();
} catch (IOException e1) {
}
//logger.log(Level.WARNING, "Incoming Message " + baos.toString());
SOAPMessage soapMessage = ((SaajSoapMessage) source).getSaajMessage();
// generate TransactionID with UUID value
String transactionID = UUID.randomUUID().toString();
// logger.log(Level.WARNING, "TransactionID=" + transactionID);
Date now = new Date();
searchMetadata.setTransactionID(transactionID);
searchMetadata.setRequestType(SearchMetadata.REQUEST_TYPE_SYNCHRONOUS);
searchMetadata.setRequestTime(now);// initialize the request time
searchMetadata.setReceivedTime(now);// mark time system receives request
searchMetadata.setVersion(version);
Map<String, Object> finalHeaders = new HashMap<String, Object>();
finalHeaders.put(HEADER_SEARCH_METADATA, searchMetadata);
if (!CollectionUtils.isEmpty(map)) {
// copy from other map
finalHeaders.putAll(map);
// check if ServiceAudit is available
SoapHeaderElement serviceAuditElement = null;
for (String key : map.keySet()) {
// logger.log(Level.WARNING, "SoapHeader.{0}", key);
if (StringUtils.contains(key, HEADER_SERVICE_AUDIT)) {
serviceAuditElement = (SoapHeaderElement) map.get(key);
break;
}
}
}
return finalHeaders;
}
// GK Key Thing here for performance improvement is avoiding marshalling
public gov.dhs.ice.ess.schema.ServiceAudit ExtractAuditHeader(Document doc) {
....
}
return serviceAudit;
}
}
Share, please, some code how would you like to see that.
Maybe you can just implement your own SoapHeaderMapper and inject it into WS Inbound Gateway?
You can still reuse your logic and copy/paste the standard behavior from the DefaultSoapHeaderMapper.
UPDATE
The test-case to demonstrate how to add user-defined header manually:
#Test
public void testCustomSoapHeaderMapper() {
DefaultSoapHeaderMapper mapper = new DefaultSoapHeaderMapper() {
#Override
protected Map<String, Object> extractUserDefinedHeaders(SoapMessage source) {
Map<String, Object> headers = super.extractUserDefinedHeaders(source);
headers.put("foo", "bar");
return headers;
}
};
mapper.setRequestHeaderNames("*");
SoapMessage soapMessage = mock(SoapMessage.class);
Map<String, Object> headers = mapper.toHeadersFromRequest(soapMessage);
assertTrue(headers.containsKey("foo"));
assertEquals("bar", headers.get("foo"));
}

WebAPI can't call Put method

I have the following code on server:
public class UploadController : ApiController
{
public void Put(string filename, string description)
{
...
}
public void Put()
{
...
}
and try to call it from client:
var clientDescr = new HttpClient();
var postData = new List<KeyValuePair<string, string>>();
postData.Add(new KeyValuePair<string, string>("filename", "test"));
postData.Add(new KeyValuePair<string, string>("description", "100"));
HttpContent contentDescr = new FormUrlEncodedContent(postData);
clientDescr.PutAsync("http://localhost:8758/api/upload", contentDescr).ContinueWith(
(postTask) =>
{
postTask.Result.EnsureSuccessStatusCode();
});
but this code calls second put method (without parameters). Why and how to call first put method correctly?
You have several options here:
You can either choose to pass the parameters in the query string, by just changing the URI to:
http://localhost:8758/api/upload?filename=test&description=100
or you can have Web API parse the form data for you by changing your action to look like this:
public void Put(FormDataCollection formData)
{
string fileName = formData.Get("fileName");
string description = formData.Get("description");
}
You can also choose to create a class that has a fileName and a description property and use that as your parameter and Web API should be able to bind it correctly for you.

Resources