Encoding A Java Object (POJO) into a JSON String in GWT - java

Objective
I'd like to use JSON-RPC to send this on the clientside:
{"jsonrpc":"2.0","method":"RegisterValues","params":[["Planets","Stars"]],"id":2}
My Attempt
Using Resty-GWT:
public interface testService extends RestService {
#Produces("application/json")
#POST
public void myCall(Params myparams, MethodCallback<Response> callback );
public class Params {
String jsonrpc = "2.0";
String method;
//String[] params;
Map<String, String> params;
int id;
public void setId(int id){
this.id = id;
}
public void setMethod(String method){
this.method = method;
}
}
public void setParams(Map<String, String> params){
this.params = params;
}
}
When I call upon the Params object in another class (to set up the call), doing something like this:
Map<Integer, String> myParams = new HashMap<Integer, String>();
myParams.put(0, "Planets");
myParams.setParams(myParams);
Obviously this then sends it like:
{"jsonrpc":"2.0", "method":"RegisterValues", "params":{"0":"Planets"}, "id":0}
I can't figure out how to get those magic: [["Planets"]]
I tried using a String[] but this gives me the result: ["Planets"]
String[][] gives the error that it isn't supported yet.
Horrible Hack
If I pass a String in my testService as:
String myparams = "{\"jsonrpc\":\"2.0\",\"method\":\"RegisterValues\",\"params\":[[\"FPlanets\"]],\"id\":2}";
It works but obviously this is horrible.
I tried using GSON to encode the POJO, but GWT doesn't allow reflections. I tried using thetransactioncompany to parse the object into a 'String' but couldn't figure out the <inherits> I needed in my gwt.xml file.
Any thoughts?

Try this one
List<List<String>> params;
instead of
Map<String, String> params;
in your Params POJO class.
Read my post here about How to create a POJO class structure from an JSON string.
below JSON string
"params":[["Planets","Stars"]]
represents a List<List<String>> where "params" is a variable name in your POJO.
POJO:
class Params implements Serializable {
private String jsonrpc;
private String method;
private int id;
private List<List<String>> params;
...
//getter/setter
//default constructor
}
Try this one at server side
String myparams = "{\"jsonrpc\":\"2.0\",\"method\":\"RegisterValues\",\"params\":[[\"FPlanets\"]],\"id\":2}";
Params params=gson.fromJson(myparams,Params.class);
System.out.println(params.params+"-"+params.id);
Send the Params object back to client.

Related

Downcasting a CompletableFuture's object type - Java/Spring Boot

I have the following classes:
public class AccountDetail {
private String accountNumber;
private Date effectiveDate;
private String status;
// a bunch of other properties
}
public class AccountDetailWithAlerts extends AccountDetail {
private LowMediumAlerts alerts;
}
public class AccountsAndAlerts {
private List<AccountDetailWithAlerts> accounts;
private HighCustomerAccountAlerts accountAlerts;
// getters and setters
}
public class CustomerAndAccountAlerts {
private List<AlertMessage> customerAlerts;
private List<AccountAlertMessages> accountAlerts;
}
public Class CompanyResponse<T> {
#JsonInclude(JsonInclude.Include.NON_NULL)
private T response;
// other things that aren't relevant
}
I have a controller, AccountsController, that does a #GetMapping and has a ResponseEntity method:
public ResponseEntity<CompanyResponse<AccountsAndAlerts> getAccountDetails {
#RequestParam MultiValueMap<String, String> queryParms,
// some #ApiParams for client-header, end-user-id & accountNumber
String accountId = queryParms.getFirst("accountId");
// setting RestHeaders, contentType
CompanyResponse<AccountsAndAlerts> response = accountDetailService.getAccountsWithAlerts(restHeaders, accountNumber, queryParms, accountId);
return new ResponseEntity<CompanyResponse<AccountsAndAlerts>>(response, headers, HttpStatus.valueOf(response.getStatus()));
}
Here is the method in accountDetailService:
public CompanyResponse<AccountsAndAlerts> getAccountsWithAlerts(RestHeaders restHeaders, String accountNumber, MultiValueMap<String, String> queryParms, String accountId) throws... {
CompanyResponse<AccountsAndAlerts> newResponse = new CompanyResponse<AccountsAndAlerts>();
try {
CompletableFuture<List<AccountDetailWithAlerts>> accountsFuture = accountDetails.getAccounts(newResponse, restHeaders, accountNumber, queryParms);
CompletableFuture<CustomerAndAccountAlerts> alertsFuture = accountDetails.getAlerts(newResponse, restHeaders, accountId);
accountsFuture.thenAcceptBoth(alertsFuture, (s1, s2) -> newResponse.setResponse(getResponse(s1, s2))).get();
} catch {
// catch code
}
return newResponse;
}
Finally, the getAccounts method in AccountDetails:
public CompletableFuture<List<AccountDetailWithAlerts>> getAccounts(CompanyResponse<AccountsAndAlerts> newResponse, RestHeaders restHeaders, String accountNumber, MultiValueMap<String, String> queryParms) throws ... {
// this has the restTemplate and the .supplyAsync()
}
What I need to do is create a new ResponseEntityMethod in the Controller:
public ResponseEntity<CompanyResponse<AccountDetail> getCertainAccountDetails
I have put in a return of that type, and I am attempting to create a new method in the accountDetailService, getCertainAccounts().
The problem is trying to set this all up without creating a whole other CompletableFuture method with an invoke and supplyAsync() and restTemplate and such.
It appears that I still need to call getAccounts(), but then I have to somewhere along this line downcast the AccountDetailWithMessages to AccountDetail. I don't know if I can somehow downcast CompletableFuture<List<AccountDetailWithAlerts>> to CompletableFuture<List<AccountDetail>> or how to do it, or if I really need to downcast CompanyResponse<AccountsAndAlerts> or how to do that.
Can anyone help?
PS. I changed the names of everything to protect my Company's code. If you see errors in methods or names or anything, please be assured that is not an issue and is just the result of my typing things out instead of copying and pasting. The only issue is how to do the downcasting.
Thanks!
PPS. In case it wasn't clear, with my new method and code I do not want to get the alerts. I am trying to get account details only without alerts.

How to get specific variable from JSON object?

I'm trying to get some data from other API and I need to get StatusCode from JSON object but I'm getting null object.
I was trying to create new class with StatusCode variable but I'm getting null.
I'm trying to get this data :
Data(data=[{"Number":"20450143160505","DateCreated":"11-06-2019 10:14:27","DocumentWeight":0.5,"CheckWeight":0,"SumBeforeCheckWeight":0,"PayerType":"Recipient","RecipientFullName":"","RecipientDateTime":"","ScheduledDeliveryDate":"12-06-2019","PaymentMethod":"Cash","CargoDescriptionString":"","CargoType":"Parcel","CitySender":"Сокільники","CityRecipient":"Київ","WarehouseRecipient":"Відділення №150 (до 30 кг): вул. Антоновича, 43 (м.\"Олімпійська\")","CounterpartyType":"PrivatePerson","Redelivery":1,"RedeliverySum":"","RedeliveryNum":"","RedeliveryPayer":"","AfterpaymentOnGoodsCost":"","ServiceType":"WarehouseWarehouse","UndeliveryReasonsSubtypeDescription":"","WarehouseRecipientNumber":150,"LastCreatedOnTheBasisNumber":"","LastCreatedOnTheBasisDocumentType":"","LastCreatedOnTheBasisPayerType":"","LastCreatedOnTheBasisDateTime":"","LastTransactionStatusGM":"","LastTransactionDateTimeGM":"","WarehouseRecipientInternetAddressRef":"916c7c93-8460-11e4-acce-0050568002cf","MarketplacePartnerToken":"","DateScan":"12:23 12.06.2019","ClientBarcode":"","SenderAddress":"","RecipientAddress":"","CounterpartySenderDescription":"","CounterpartyRecipientDescription":"","CounterpartySenderType":"Organization","PaymentStatus":"","PaymentStatusDate":"","AmountToPay":"","AmountPaid":"","WarehouseRecipientRef":"916c7c94-8460-11e4-acce-0050568002cf","DocumentCost":40,"AnnouncedPrice":"","OwnerDocumentNumber":"","DateFirstDayStorage":"2019-06-21","InternationalDeliveryType":"","DaysStorageCargo":"","RecipientWarehouseTypeRef":"841339c7-591a-42e2-8233-7a0a00f0ed6f","StorageAmount":"","StoragePrice":"","VolumeWeight":"0.50","SeatsAmount":"1","OwnerDocumentType":"","ActualDeliveryDate":"2019-06-12 12:23:22","DateReturnCargo":"","CardMaskedNumber":"","Status":"Прибув у відділення","StatusCode":"7","RefEW":"8ed817ef-8c18-11e9-91ff-0025b501a04b","RedeliveryPaymentCardRef":"","RedeliveryPaymentCardDescription":"","CreatedOnTheBasis":"","DatePayedKeeping":"2019-06-21 00:00:00","OnlineCreditStatusCode":"","OnlineCreditStatus":""}])
Method to get data :
RestTemplate restTemplate = new RestTemplate();
Data string = restTemplate.postForObject(blalba,blabla,Data.class)
And my class
public class Data {
#JsonProperty("data")
private JsonNode data;
//get set
}
There are a few ways to achieve it:
Using a Map<String, Object>
You could read the response payload as a Map<String, Object>:
ParameterizedTypeReference<HashMap<String, Object>> responseType =
new ParameterizedTypeReference<HashMap<String, Object>>() {};
Map<String, Object> responsePayload =
restTemplate.exchange(purchaseRequestDetailsEndpoint, HttpMethod.POST,
new HttpEntity<>(requestPayload), responseType);
String statusCode = responsePayload.get("StatusCode");
Mapping only the properties you need
Define a class mapping the properties you need:
#Data
#JsonIgnoreProperties(ignoreUnknown = true)
public class ResponsePayload {
#JsonProperty("StatusCode")
private String statusCode;
}
And read the response payload using the class defined above:
ResponsePayload responsePayload =
restTemplate.postForObject(uri, request, ResponsePayload.class);
String statusCode = responsePayload.getStatusCode();
Alternatively you could map the properties you need and store the rest in a map:
#Data
public class ResponsePayload {
#JsonProperty("StatusCode")
private String statusCode;
#JsonAnySetter
private Map<String, Object> properties = new HashMap<>();
#JsonIgnore
public Object get(String key) {
return properties.get(key);
}
}

How to use Map to bound a values on parameters of java method from URI in Rest API

#GET
#Path("/getResults/{names}/view")
#Produces("application/json")
public String getResults(#QueryParam("names") Map<String, String> names) {
System.out.println(names);
return "someValue";
}
Explanation: We are trying to bind a value to java.util.map parameter from URI, but not getting it.
Accept string as Query params.
Then convert the string to map using gson.
#GET
#Path("/getResults/{names}/view")
#Produces("application/json")
public String getResults(#QueryParam("names") String names) {
Map<String, String> map = new Gson().fromJson(names, Map.class)
return "someValue";
}

Jackson Serialization / Deserialization: Dynamic properties and fields

I use Spring MVC to drive the API of an application I am currently working with. The serialization of the API response is done via Jackson's ObjectMapper. I am faced with the following situation, we are extending a number of our objects to support UserDefinedFields (UDF) which is shown below in the abstract UserDefinedResponse. Being a SaaS solution, multiple clients have different configuration that is stored in the database for their custom fields.
The goal of this question is to be able to respond to each client with their UDF data. This would require
Dynamically rename the fields customString1, customString2, ... to their corresponding UDF labels
Remove undefined UDF fields (Example client uses only 2 out of the 4 fields.
Example of the abstract response
public abstract class UserDefinedResponse {
public String customString1;
public String customString2;
public String customString3;
public String customString4;
}
And response for a product that extends the UserDefinedResponse object
public class Product extends UserDefinedResponse {
public long id;
public String name;
public float price;
}
And finally, assuming a client sets
customString1 = "supplier"
customString2 = "warehouse"
Serializing Product for this customer should result in something similar to this:
{
"id" : 1234,
"name" : "MacBook Air",
"price" : 1299,
"supplier" : "Apple",
"warehouse" : "New York warehouse"
}
I think you could do what you need with the help of a few Jackson annotations:
public abstract class UserDefinedResponse {
#JsonIgnore
public String customString1;
#JsonIgnore
public String customString2;
#JsonIgnore
public String customString3;
#JsonIgnore
public String customString4;
#JsonIgnore // Remove if clientId must be serialized
public String clientId;
private Map<String, Object> dynamicProperties = new HashMap<>();
#JsonAnyGetter
public Map<String, Object> getDynamicProperties() {
Mapper.fillDynamicProperties(this, this.dynamicProperties);
return this.dynamicProperties;
}
#JsonAnySetter
public void setDynamicProperty(String name, Object value) {
this.dynamicProperties.put(name, value);
Mapper.setDynamicProperty(this.dynamicProperties, name, this);
}
}
First, annotate all the properties of your base class with #JsonIgnore, as these won't be part of the response. Then, make use of the #JsonAnyGetter annotation to flatten the dynamicProperties map, which will hold the dynamic properties. Finally, the #JsonAnySetter annotation is meant to be used by Jackson on deserialization.
The missing part is the Mapper utility class:
public abstract class Mapper<T extends UserDefinedResponse> {
private static final Map<Class<T>, Map<String, Mapper<T>>> MAPPERS = new HashMap<>();
static {
// Mappers for Products
Map<String, Mapper<Product>> productMappers = new HashMap<>();
productMappers.put("CLIENT_1", new ProductMapperClient1());
productMappers.put("CLIENT_2", new ProductMapperClient2());
// etc for rest of clients
MAPPERS.put(Product.class, productMappers);
// Mappers for Providers
Map<String, Mapper<Provider>> providerMappers = new HashMap<>();
providerMappers.put("CLIENT_1", new ProviderMapperClient1());
providerMappers.put("CLIENT_2", new ProviderMapperClient2());
// etc for rest of clients
MAPPERS.put(Provider.class, providerMappers);
// etc for rest of entities
// (each entity needs to add specific mappers for every client)
}
protected Mapper() {
}
public static void fillDynamicProperties(T response, Map<String, Object> dynamicProperties) {
// Get mapper for entity and client
Mapper<T> mapper = MAPPERS.get(response.getClass()).get(response.clientId);
// Perform entity -> map mapping
mapper.mapFromEntity(response, dynamicProperties);
}
public static void setDynamicProperty(Map<String, Object> dynamicProperties, String name, T response) {
// Get mapper for entity and client
Mapper<T> mapper = MAPPERS.get(response.getClass()).get(response.clientId);
// Perform map -> entity mapping
mapper.mapToEntity(dynamicProperties, name, response);
}
protected abstract void mapFromEntity(T response, Map<String, Object> dynamicProperties);
protected abstract void mapToEntity(Map<String, Object> dynamicProperties, String name, T response);
}
And for Product entity and client CLIENT_1:
public class ProductMapperClient1 extends Mapper<Product> {
#Override
protected void mapFromEntity(Product response, Map<String, Object> dynamicProperties) {
// Actual mapping from Product and CLIENT_1 to map
dynamicProperties.put("supplier", response.customString1);
dynamicProperties.put("warehouse", response.customString2);
}
#Override
protected void mapToEntity(Map<String, Object> dynamicProperties, String name, Product response) {
// Actual mapping from map and CLIENT_1 to Product
String property = (String) dynamicProperties.get(name);
if ("supplier".equals(name)) {
response.customString1 = property;
} else if ("warehouse".equals(name)) {
response.customString2 = property;
}
}
}
The idea is that there's a specific mapper for each (entity, client) pair. If you have many entities and/or clients, then you might consider filling the map of mappers dynamically, maybe reading from some config file and using reflection to read the properties of the entity.
Have you considered returning Map<> as a response? Or a part of the response, like response.getUDF().get("customStringX"))? This should save you some possible trouble in the future, e.g.: 10 millions of concurrent users means 10 million classes in your VM.

Spring RequestParam Map<String, String>

I have this in my controller:
#RequestMapping(value = "/myUrl", method = RequestMethod.GET)
public String myUrl(#RequestParam(value = "test") Map<String, String> test)
{
return test.toString();
}
And I'm making this HTTP request:
GET http://localhost:8080/myUrl?test[a]=1&test[b]=2
But in the logs I'm getting this error:
org.springframework.web.bind.MissingServletRequestParameterException: Required Map parameter 'test' is not present
How can I pass Map<String, String> to Spring?
May be it's a bit late but this can be made to work by declaring an intermediate class:
public static class AttributeMap {
private Map<String, String> attrs;
public Map<String, String> getAttrs() {
return attrs;
}
public void setAttrs(Map<String, String> attrs) {
this.attrs = attrs;
}
}
And using it as parameter type in method declaration (w/o #RequestParam):
#RequestMapping(value = "/myUrl", method = RequestMethod.GET)
public String myUrl(AttributeMap test)
Then with a request URL like this:
http://localhost:8080/myUrl?attrs[1]=b&attrs[222]=aaa
In test.attrs map all the attributes will present as expected.
It's not immediately clear what you are trying to do since test[a] and test[b] are completely unrelated query string parameters.
You can simply remove the value attribute of #RequestParam to have your Map parameter contain two entries, like so
{test[b]=2, test[a]=1}

Categories

Resources