I have a DTO, which looks a bit like this:
class Response {
Long id;
Locale locale;
Map<Integer,QuestionResponse> questionResponses=new HashMap<Integer,QuestionResponse>();
...
}
But I'm having trouble mapping a ColumnConfig to the value property of a questionResponses map entry. For example, I want something like this:
beanModel.get("questionResponses[15].value")
I can see from the get method in BeanModelData that I should be able to get a property of a Map but can't figure out the syntax. Any help would be really appreciated.
One solution is to use a DataReader to map your Response to a new Model
final ListLoader<BaseListLoadResult<BeanModel>> loader = new BaseListLoader<BaseListLoadResult<BeanModel>>(
proxy, new MyBeanModelReader());
loader.load();
new ListStore<BeanModel>(loader); //TODO
public class MyBeanModelReader implements DataReader<List<BeanModel>> {
private BeanModelReader reader = new BeanModelReader();
public boolean isFactoryForEachBean() {
return reader.isFactoryForEachBean();
}
public List<BeanModel> read(Object loadConfig, Object data) {
List<BeanModel> newModelsData = new ArrayList<BeanModel>();
ListLoadResult<ModelData> models = reader.read(loadConfig, data);
List<ModelData> modelsData = models.getData();
for (ModelData modelData : modelsData) {
BeanModel model = (BeanModel) modelData;
Response response = (Response) model.getBean();
model.set("id", response.getId());
model.set("locale", response.getLocale());
model.set("QuestionResponse15", response.getQuestionResponse().get(...)); //retrieve the QR you want
newModelsData.add(model);
}
return newModelsData;
}
public void setFactoryForEachBean(boolean factoryForEachBean) {
reader.setFactoryForEachBean(factoryForEachBean);
}
}
Related
I have two apis which is return following json responses.
I created a class called 'Card' and how should I achieve 'expiry' field which have different type for particular request.
Thanks in advance.
For simplicity i'll work only with card object from example, i won't be wrapping it another object. To avoid duplication in examples, i'll use this base class to hold common fields:
public abstract class BaseCard {
private String brand;
private String fundingMethod;
private String scheme;
//setters and getters
}
You have few options.
Option 1:
If you know which api you are getting response from, you can have two classes, each holding expiry in format specific for the api. When getting data from api 1 use:
public class CardApi1 extends BaseCard {
private String expiry;
//setters and getters
}
And for api 2:
public class CardApi2 extends BaseCard {
private Expiry expiry;
//setters and getters
}
The expiry object looks like this:
public class Expiry {
private String month;
private String year;
//setters and getters
}
If you are calling api 1, deserialize to CardApi1, else to CardApi2.
Option 2: Make the expiry field Object, this way anything can be deserialized into it.
public class CardApiMixed extends BaseCard {
private Object expiry;
//setters and getters
public String getExpiryAsString() {
return (String) this.expiry;
}
public Map<String, Object> getExpiryAsMap() {
#SuppressWarnings("unchecked")
Map<String, Object> expiry = (Map<String, Object>) this.expiry;
return expiry;
}
}
This way no matter which api you are calling, you deserialize into one class. The downside is, when retrieving expiry, you still have to know which api the data comes from, so you use correct method.
Option 3: Write custom deserializer which will resolve the field correctly, no matter which api is used. Personally i would go for this option. The deserializer is this:
public class ExpiryResolvingCardDeserializer extends StdDeserializer<CardApiResolved> {
public ExpiryResolvingCardDeserializer() {
super(CardApiResolved.class);
}
#Override
public CardApiResolved deserialize(JsonParser parser, DeserializationContext context) throws IOException {
JsonNode node = parser.getCodec().readTree(parser);
CardApiResolved card = new CardApiResolved();
card.setBrand(node.get("brand").asText());
card.setFundingMethod(node.get("fundingMethod").asText());
card.setScheme(node.get("scheme").asText());
JsonNode expiryNode = node.get("expiry");
Expiry expiry = new Expiry();
if (expiryNode.isObject()) {
//that's for api 2
expiry.setMonth(expiryNode.get("month").asText());
expiry.setYear(expiryNode.get("year").asText());
} else {
//for api 1
String text = expiryNode.asText();
//assuming format is always mmYY, you can handle it in differently if there are other options
int splitIndex = 2;
expiry.setMonth(text.substring(0, splitIndex));
expiry.setYear(text.substring(splitIndex));
}
card.setExpiry(expiry);
return card;
}
}
To summarise it, when expiry node is object, handle it like an object to get month and year data from it, if it is a string, split it to extract month and year. Like this no matter the format, expiry is always resolved to Expiry class. The card class will look like this:
#JsonDeserialize(using = ExpiryResolvingCardDeserializer.class)
public class CardApiResolved extends BaseCard {
private Expiry expiry;
//setters and getters
}
Note the JsonDeserialize annotation specifying the deserializer to use for this type. And lastly, some unit tests to play with and check results. The api responses are files in the test resources.
public class CardApiTests {
private final ObjectMapper mapper = new ObjectMapper();
#Test
public void testCardApi1() throws Exception {
InputStream inputStream = ClassLoader.getSystemResourceAsStream("card-api-1.json");
//map to CardApi1, when calling api 1
CardApi1 result = this.mapper.readValue(inputStream, CardApi1.class);
assertEquals("0139", result.getExpiry());
}
#Test
public void testCardApi2() throws Exception {
InputStream inputStream = ClassLoader.getSystemResourceAsStream("card-api-2.json");
//map to CardApi2, when calling api 2
CardApi2 result = this.mapper.readValue(inputStream, CardApi2.class);
assertEquals("1", result.getExpiry().getMonth());
assertEquals("39", result.getExpiry().getYear());
}
#Test
public void testCardApiMixed_Api1() throws Exception {
InputStream inputStream = ClassLoader.getSystemResourceAsStream("card-api-1.json");
CardApiMixed result = this.mapper.readValue(inputStream, CardApiMixed.class);
assertEquals("0139", result.getExpiryAsString());
}
#Test
public void testCardApiMixed_Api2() throws Exception {
InputStream inputStream = ClassLoader.getSystemResourceAsStream("card-api-2.json");
CardApiMixed result = this.mapper.readValue(inputStream, CardApiMixed.class);
assertEquals("1", result.getExpiryAsMap().get("month"));
assertEquals("39", result.getExpiryAsMap().get("year"));
}
#Test
public void testCardApiResolved_Api1() throws Exception {
InputStream inputStream = ClassLoader.getSystemResourceAsStream("card-api-1.json");
CardApiResolved result = this.mapper.readValue(inputStream, CardApiResolved.class);
assertEquals("01", result.getExpiry().getMonth());
assertEquals("39", result.getExpiry().getYear());
}
#Test
public void testCardApiResolved_Api2() throws Exception {
InputStream inputStream = ClassLoader.getSystemResourceAsStream("card-api-2.json");
CardApiResolved result = this.mapper.readValue(inputStream, CardApiResolved.class);
assertEquals("1", result.getExpiry().getMonth());
assertEquals("39", result.getExpiry().getYear());
}
}
Can you please share how the expiry field is prepared? like
"expiery":"0139" = month+year.
if the field is prepared like this then you can keep variable type either String or list, you have needed a minor modification. You will give an idea according to the given example.
public class App {
private static final HttpClient httpClient = HttpClient.newBuilder()
.version(HttpClient.Version.HTTP_1_1)
.connectTimeout(Duration.ofSeconds(10))
.build();
public static void main(String[] args) throws IOException, InterruptedException {
HttpRequest request = HttpRequest.newBuilder()
.GET()
.uri(URI.create("https://jsonmock.abc.com/api/articles?page=3"))
.setHeader("User-Agent", "Java 11 HttpClient Bot") // add request header
.build();
HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
// print response headers
HttpHeaders headers = response.headers();
headers.map().forEach((k, v) -> System.out.println(k + ":" + v));
String responseBody = response.body();
ObjectMapper mapper = new ObjectMapper();
Object newJsonNode = mapper.readValue(responseBody, Object.class);
Map<String, Object> objectMap = (Map) newJsonNode;
if (objectMap.get("expiry") instanceof List) {
//if I assume that expiry string created by appending month and year
List<Map<String, Object>> list = (List) objectMap.get("expiry");
Map<String, Object> map = list.get(0);
String exp = (String) map.get("month") + (String) map.get("year");
objectMap.put("expiry", exp);
//then parse to direct object
} else if (objectMap.get("expiry") instanceof String) {
//then cast to dto
//you can apply reverse logic here, i.e string to map
}
}
}
I have method which takes two parameters and returns three values from properties file when called.
I have written a sample code and its working. I just want to know is there any better way of doing this?
Below is the code:
#WebService(serviceName = "HS_WebService")
public class HS_Service
{
ResponseInfo rd5 = new ResponseInfo();
#WebMethod(operationName = "initiateBatchProcess")
public #WebResult(name = "Response") ArrayList initiateBatchProcess(#WebParam (name = "BatchID")int BatchId, #WebParam (name = "MPTRef")String MPTRef) throws Exception
{
return rd5.initiateBatchProcess();
}
}
Here is the other class which is reading the values from properties file and passing to the above method (rd5.initiateBatchProcess();)
public class ResponseInfo
{
static Properties props = new Properties();
ArrayList list = new ArrayList();
public ArrayList initiateBatchProcess() throws Exception
{
props.load(ResponseInfo.class.getResourceAsStream("ResponseFields.properties"));
String method1_status = props.getProperty("method1_status");
String method1_comments = props.getProperty("method1_comments");
list.add(method1_status);
list.add(method1_comments);
return list;
}
I am using array list to achieve this. Is there any better way of doing this?
Using Flexjson, I am trying to serialize an object ("Payload") that contains a List. Each MyBean has a field "items", which is a Map>. When I serialize this Payload object, the map field ("items") is empty.
public class Payload {
private List<MyBean> myBeans = new ArrayList<MyBean>();
//the JSON returned has blank values for myBeans.values.items
public String toJson() {
return new JSONSerializer()
.exclude("*.class")
.include("myBeans")
.serialize(this);
}
}
However, when I serialize the MyBean object directly, it works fine.
public class MyBean {
private Map<String, List<SomeBean>> items = new HashMap<String, List<SomeBean>>();
//this works
public String toJson() {
return new JSONSerializer()
.exclude("*.class")
.deepSerialize(this);
}
}
Any suggestions?
After trying a bunch of things, I found this solution.
I created a custom transformer for maps. Just copied the Flexjson MapTransformer and commented out a IF condition. New code below
public class Payload {
private List<MyBean> myBeans = new ArrayList<MyBean>();
//the JSON returned has blank values for myBeans.values.items
public String toJson() {
return new JSONSerializer()
.exclude("*.class")
.include("myBeans")
.transform(new SOMapTransformer(), Map.class)
.serialize(this);
}
}
public class MyBean {
private Map<String, List<SomeBean>> items = new HashMap<String, List<SomeBean>>();
//this works
public String toJson() {
return new JSONSerializer()
.exclude("*.class")
.transform(new SOMapTransformer(), "items")
.deepSerialize(this);
}
}
Here is the custom SOMapTransformer:
import com.mycompany.mypackage.SomeBean;
import flexjson.JSONContext;
import flexjson.Path;
import flexjson.TypeContext;
import flexjson.transformer.AbstractTransformer;
import flexjson.transformer.TransformerWrapper;
public class SOMapTransformer extends AbstractTransformer {
public void transform(Object object) {
JSONContext context = getContext();
Path path = context.getPath();
Map<String, List<SomeBean>> value = (Map<String, List<SomeBean>>) object;
TypeContext typeContext = getContext().writeOpenObject();
for (Object key : value.keySet()) {
path.enqueue((String) key);
//DPD 2013-11-04: This bloody line of code cost me 12 hours. Comment it out!
// if (context.isIncluded((String) key, value.get(key))) {
TransformerWrapper transformer = (TransformerWrapper)context.getTransformer(value.get(key));
if(!transformer.isInline()) {
if (!typeContext.isFirst()) getContext().writeComma();
typeContext.setFirst(false);
getContext().writeName(key.toString());
}
typeContext.setPropertyName(key.toString());
transformer.transform(value.get(key));
// }
path.pop();
}
getContext().writeCloseObject();
}
to sum it up before the wall of text below :-)
I need help with how to deserialize a Dictionary using Jackson and a custom deserializer.
Right now I have an Android app communication with a .NET (C#) server. They use JSON to communicate.
On the JAVA-side, I am using Jackson to handle the JSON and on the .NET-side I am using the built in DataContractSerializer (I know, ppl will start commenting I should use something else, but Im not so... ;-) )
The problem is that I am sending Dictionaries from C# and I want that to be deserialized to HashMaps om the JAVA-side, but I havent found a good resource for how to do that.
One example of a Dictionary I am sending from C#:
// Here, the object Equipment is the key, and the int following indicates the amount
[DataMember]
public Dictionary<Equipment, int> EquipmentList { get; set; }
And just for reference, the Equipment object in C#:
[DataContract]
public class Equipment
{
[DataMember]
public uint Id { get; set; }
[DataMember]
public string Name { get; set; }
public override bool Equals(object obj)
{
if (obj.GetType() != this.GetType())
return false;
Equipment e = (Equipment)obj;
return e.Id == this.Id;
}
}
Its serialized correctly into decent JSON on the C#-side, the Dictionary looks like this:
//....
"EquipmentList":[
{
"Key":{
"EquipmentId":123,
"Name":"MyName"
},
"Value":1
}
//....
I have added a custom serializer (CustomMapSerializer), like this:
public static ObjectMapper mapper = new ObjectMapper();
private static SimpleDeserializers simpleDeserializers = new SimpleDeserializers();
private static StdDeserializerProvider sp = new StdDeserializerProvider();
public static void InitSerialization()
{
simpleDeserializers.addDeserializer(String.class, new CustomStringDeserializer());
simpleDeserializers.addDeserializer(Map.class, new CustomMapDeserializer());
sp.withAdditionalDeserializers(simpleDeserializers);
mapper.setDeserializerProvider(sp);
}
And decorated the field like this:
#JsonDeserialize(using=CustomMapDeserializer.class)
public Map<Equipment, Integer> EquipmentList;
And finally, when I run it I do get a break in the custom deserializer class, but I am not sure how to proceed from here:
public class CustomMapDeserializer extends JsonDeserializer<Map> {
#Override
public Map deserialize(JsonParser arg0, DeserializationContext arg1) throws IOException, JsonProcessingException
{
return new HashMap<Object, Object>(); // <-- I can break here
}
}
So, what I would like is some input on how to create a HashMap with the correct values in it, ie a deserialized Equipment as Key and an Int as value.
Anyone out there who can assist? =)
Ok, after a while testing and researching, this is what I came up with.
The custom deserializer looks like this:
public class CustomMapCoTravellerDeserializer extends JsonDeserializer<Map> {
#Override
public Map deserialize(JsonParser jp, DeserializationContext arg1) throws IOException, JsonProcessingException
{
HashMap<CoTravellers, Integer> myMap = new HashMap<CoTravellers, Integer>();
CoTravellers ct = new CoTravellers();
jp.nextToken(); // {
jp.nextToken(); // Key
jp.nextToken(); // {
jp.nextToken(); // "CoTravellerId"
jp.nextToken(); // CoTravellerId Id
int coTravellerValue = jp.getIntValue();
jp.nextToken(); // Name
jp.nextToken(); // Name Value
String coTravellerName = jp.getText();
jp.nextToken(); // }
jp.nextToken(); // "Value"
jp.nextToken(); // The value
int nbr = jp.getIntValue();
ct.CoTravellerId = coTravellerValue;
ct.Name = coTravellerName;
myMap.put(ct, nbr);
return myMap;
}
}
I think this will work, if I can only figure out the JsonMappingException I am getting... but I will post on that separately =)
I am facing problem while deserializing to below entity using Javascript Serializer. Please help
JSON String:
{"AccountNo":0,"EmailAddress":"test#gmail.com","Destination_Prefernce":[{"Value":"Test Data"}]}
Java Code
public class EMailPreferenceEntity
{
private int _accountNo;
private string emailAddress;
private DestinationPreferences _destinationPrefernce = new DestinationPreferences();
public int AccountNo
{
get { return _accountNo; }
set { _accountNo = value; }
}
public string EmailAddress
{
get { return emailAddress; }
set { emailAddress = value; }
}
public DestinationPreferences Destination_Prefernce
{
get { return _destinationPrefernce; }
set { _destinationPrefernce = value; }
}
}
Handler File:
public class AjaxHandler : IHttpHandler, IRequiresSessionState
{
public void ProcessRequest (HttpContext context) {
string jsData = context.Request["Data"];
if (!string.IsNullOrEmpty(jsData))
{
JavaScriptSerializer ser = new JavaScriptSerializer();
EMailPreferenceEntity jsEntity = ser.Deserialize<EMailPreferenceEntity>(jsData);
}
}
Type erasure means your List will just become List after compilation so, when your http request arrives, it will try to deserialize List, and probably won't hit whatever you registered for List.
I'm not sure how your serializer handles it, but in Gson's case, you create a TypeToken out of the generic, so that the connection between type and serializer doesn't get lost after compilation.