Deserialize an object's property which has an inconsistent name? - java

Using Retrofit here to consume Google Civic API.
The library requires you to create a model of what the API will return as I have done already with Election. Which is basically a copy of the google documentation.
(Retrofit binds the response properties to properties with the same name)
Election.Java :
public class Election {
private long id;
private String name;
private String electionDay;
private String ocdDivisionId;
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getElectionDay() {
return electionDay;
}
public void setElectionDay(String electionDay) {
this.electionDay = electionDay;
}
public String getOcdDivisionId() {
return ocdDivisionId;
}
public void setOcdDivisionId(String ocdDivisionId) {
this.ocdDivisionId = ocdDivisionId;
}
}
But Representatives have an inconsistent property name, thus I don't see a way to model this in a way Retrofit will know how to deserialize the API's response.
Representatives object (JSON) :
property name is called (key)
How do I let Retrofit deserialize a model that captures the property named variable after a key of the division?

Assuming you're using a Gson converter, I personally would use a map. I guess the same can be achieved with other converters, but I never used them. Say you have the following object:
public class Division {
#SerializedName("name")
#Expose
private String name;
#SerializedName("alsoKnownAs")
#Expose
private List<String> alsoKnownAs = new ArrayList<>();
#SerializedName("officeIndices")
#Expose
private List<Integer> officeIndices = new ArrayList<>();
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<String> getAlsoKnownAs() {
return alsoKnownAs;
}
public void setAlsoKnownAs(List<String> alsoKnownAs) {
this.alsoKnownAs = alsoKnownAs;
}
public List<Integer> getOfficeIndices() {
return officeIndices;
}
public void setOfficeIndices(List<Integer> officeIndices) {
this.officeIndices = officeIndices;
}
}
Which represents the object inside the divisions array. You can then have the class:
private class Divisions {
#SerializedName("divisions")
#Expose
private Map<String, Division> divisions = new HashMap<>();
// ...
}
Notice the usage of a map here? Behind the scenes Gson will be able to serialise and deserialise your objects. The class Divisions is the root of the json you gave us in the question.
Hope this helps

Related

Use Gson to serialize complex set

I have a method, setFriends(), that takes a Set. This method is in another module and I want to send setFriends() serialized data via Gson().fromJson(). I am not sure if I have the arg string correct. I have tried the following which failed:
// my attempt to serialize
String arg = "[Friend[name=Dave, relationship=Relationship[Work]], Friend[name=Jack, relationship=Relationship[School]]]"; // not sure if this string is correct
Type type = new TypeToken<Set<Friend>>(){}.getType();
Set<Friend> payload = new Gson().fromJson(arg, type);
sendPayload(payload); // will send payload to People.setFriends()
// code from the other module:
Set<Friend>
public class People {
public void setFriends(Set<Friend> friends) { ... }
}
public class Friend {
String name;
Relationship relationship;
}
public enum Relationship {
School,
Work
}
Google Gson is a simple Java-based library to serialize Java objects to JSON and vice versa. In JSON we represent objects using {}. For nested objects we use "fieldName": {<nested object>}.
In your example I've considered relationship as an enum. And introduced Address as a nested object. JSON for the Friend object will look like this:
{
"name" : "Dave",
"relationship" : "WORK",
"address" : {"street" : "s1"}
}
There is no representation for Set structure in JSON so you put elements in list.Following is the sample java code:
public class Test
{
public static void main(String[] args)
{
String arg = "["
+ "{\"name\":\"Dave\",\"relationship\":\"WORK\",\"address\":{\"street\":\"s1\"}},"
+ "{\"name\":\"Jack\",\"relationship\":\"SCHOOL\",\"address\":{\"street\":\"s2\"}},"
+ "{\"name\":\"Dave\",\"relationship\":\"GYM\",\"address\":{\"street\":\"s3\"}}"
+ "]";
Type type = new TypeToken<Set<Friend>>(){}.getType();
Set<Friend> payload = new Gson().fromJson(arg, type);
sendPayload(payload);
}
static void sendPayload(Set<Friend> plod) {
System.out.println("Sent payload: " + plod.toString());
}
}
class Friend {
private String name;
private Relationship relationship;
private Address address;
public Address getAddress() {
return address;
}
public void setAddress(Address address) {
this.address = address;
}
public String getName() {
return name;
}
public Relationship getRelationship() {
return relationship;
}
public void setRelationship(Relationship relationship) {
this.relationship = relationship;
}
public void setName(String name) {
this.name = name;
}
public String toString() {
return this.name;
}
#Override
public int hashCode() {
return name.hashCode();
}
#Override
public boolean equals(Object obj) {
return name.equals(obj.toString());
}
}
enum Relationship {
WORK, SCHOOL, GYM
}
class Address {
String street;
public String getStreet() {
return street;
}
public void setStreet(String street) {
this.street = street;
}
}
Output:
Sent payload: [Dave, Jack]
In my environment Gson returned LinkedHashSet object. If you want different set like sorted ones. Then you can deserialize the Json into List<Freind> then convert it to the desired set.

GSON conversion error when a null field contains subfields

I am trying to parse the JSON result from the Wordpress plugins API using Retrofit2 and GSON. I have generated my POJO using the well known website and modified it into the following model:
PluginsApiResponse.java
public class PluginsApiResponse {
#SerializedName("plugins")
#Expose
private List<Plugin> plugins = null;
public List<Plugin> getPlugins() {
return plugins;
}
public void setPlugins(List<Plugin> plugins) {
this.plugins = plugins;
}
}
Plugin.java
public class Plugin {
#SerializedName("name")
#Expose
private String name;
#SerializedName("homepage")
#Expose
private String homepage;
#SerializedName("screenshots")
#Expose
private Screenshots screenshots;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getHomepage() {
return homepage;
}
public void setHomepage(String homepage) {
this.homepage = homepage;
}
public Screenshots getScreenshots() {
return screenshots;
}
public void setScreenshots(Screenshots screenshots) {
this.screenshots = screenshots;
}
}
Screenshots.java
public class Screenshots {
#SerializedName("1")
#Expose
private com.dkalsan.retrofitwordpress._1 _1;
#SerializedName("2")
#Expose
private com.dkalsan.retrofitwordpress._2 _2;
#SerializedName("3")
#Expose
private com.dkalsan.retrofitwordpress._3 _3;
public com.dkalsan.retrofitwordpress._1 get1() {
return _1;
}
public void set1(com.dkalsan.retrofitwordpress._1 _1) {
this._1 = _1;
}
public com.dkalsan.retrofitwordpress._2 get2() {
return _2;
}
public void set2(com.dkalsan.retrofitwordpress._2 _2) {
this._2 = _2;
}
public com.dkalsan.retrofitwordpress._3 get3() {
return _3;
}
public void set3(com.dkalsan.retrofitwordpress._3 _3) {
this._3 = _3;
}
}
_1.java (_2.java and _3.java are identical)
public class _1 {
#SerializedName("src")
#Expose
private String src;
#SerializedName("caption")
#Expose
private String caption;
public String getSrc() {
return src;
}
public void setSrc(String src) {
this.src = src;
}
public String getCaption() {
return caption;
}
public void setCaption(String caption) {
this.caption = caption;
}
}
The problem occurs in case the screenshots field contains no entries. I've set up the HttpLoggingInterceptor, which logs the response code 200 and the json in its entirety. I've also excluded the possibility of it being the internet connectivity issue according to the following article. If I remove the screenshots field from the model there is no trouble parsing. Is it possible that the error persists due to GSON trying to deserialize the nonexistent fields 1, 2, and 3 and if so, how to deal with it?
Turn out the problem was in the JSON response formatting. If there were no screenshots it was formatted as a JSON array, otherwise it was formatted as a JSON object containing objects 1, 2, 3, etc. I've managed to fix it by following the answer on this stackoverflow question.

Using Strings from resources in List.

I'm new to Android and I'm having a problem using String variables from resources in my code. I tried a couple of solutions found on the internet and Android API Guides, but they didn't work in this specific case, could also be me not using them correctly.
To be more specific, I have a Master/Detail flow activity and I would like to use resource strings as item names for multilanguage purposes, but I have a problem with recovering actual strings.
The error I get is:
Cannot resolve method 'getString()'
Here is my code based on android studio dummy file
public class Categories {
public static List<CatName> ITEMS = new ArrayList<CatName>();
static {
String temp = getString(R.string.cat_n1);
addItem(new CatName("1", temp);
}
private static void addItem(CatName item) {
ITEMS.add(item);
}
public static class CatName {
public String id;
public String name;
public FieldCat(String id, String name) {
this.id = id;
this.name = name;
}
#Override
public String toString() {
return name;
}
}}
You need to specify the resource. Try this,
getResources().getString(R.string.cat_n1);
getString(int resId): Return a localized string from the application's package's default string table.
getResources().getString(int id): Returns the string value associated with a particular resource ID. It will be stripped of any styled text information.
Try using it with a constructor passing the context and calling getstring on that
public class Categories {
public static List<CatName> ITEMS = new ArrayList<CatName>();
public Categories(Context ct)
{
String temp = ct.getString(R.string.abc_action_bar_home_description);
addItem(new CatName("1", temp));
}
private static void addItem(CatName item) {
ITEMS.add(item);
}
public static class CatName {
public String id;
public String name;
public CatName(String id, String name) {
this.id = id;
this.name = name;
}
#Override
public String toString() {
return name;
}
}}

Cannot bind RemoteObject from BlazeDS

I'm using BlazeDS in Tomcat7 and Flex. I'm trying to use custom classes between the client and server.
In as:
package
{
[Bindable]
[RemoteClass(alias="remoting.Product")]
public class Product
{
public var name:String;
public var id:int;
public var isVisible:Boolean;
}
}
In Java:
package remoting;
public class Product {
public String name;
public int id;
public Boolean isVisible;
public Product(){
name = "Product 0.1";
id = 123;
isVisible = false;
}
public void setName(String _name){
name = _name;
}
public void setId(int _id){
id = _id;
}
public void setVisible(Boolean _isVisible){
isVisible = _isVisible;
}
}
Service part:
public Product echo() {
Product product = new Product();
product.setId(123);
product.setName("My Product");
product.setVisible(true);
return product;
}
I can successfully set the destination of the RemoteObject and call the echo() method. The result event fires up, with the Product object in event.result. However, it does not contain any sensible data. The variables from AS class just get initialized with null, 0 and true values. I'm wondering what's the problem. I tried returning a String with parameters from Product and it works fine, so they get set fine. The problem is in mapping.
I could go another way and implement Externalizable but I don't understand this part from the example:
name = (String)in.readObject();
properties = (Map)in.readObject();
price = in.readFloat();
What if there is a number of strings?
Cheers.
In java class: use private fields and implement getters.
package remoting;
public class Product {
private String name;
private int id;
private Boolean isVisible;
public Product() {
name = "Product 0.1";
id = 123;
isVisible = false;
}
public void setName(String _name){
name = _name;
}
public String getName(){
return name;
}
public void setId(int _id){
id = _id;
}
public int getId(){
return id;
}
public void setIsVisible(Boolean _isVisible){
isVisible = _isVisible;
}
public Boolean getIsVisible() {
return isVisible;
}
}
You could also switch from BlazeDS to GraniteDS: the latter has a powerful transparent externalization mechanism as well as code generation tools that can really save your time (see documentation here).

How to update the one of the value in MAP

I have a map object testMap declared as HashMap<String, Test>.
Test is a simple class which contains references to an Object and to two String values.
ie,
public class Test {
private String name;
private String id;
private Object val;
public Test(Object val,String name.String id){
this.val =val;
this.id=id;
this.name = name;
}
I want to update the "name" only in the Hash map "testMap." How can I do this?
Test test = testMap.get("key");
if (test != null) {
test.name = "new name";
}
You need to change the visibility of your name property to public or add a setter to it:
public class Test {
private String name;
private String id;
private Object val;
public Test(Object val,String name, String id){
this.val =val;
this.id = id;
this.name = name;
}
public void setName(String name) {
this.name = name;
}
Then, to change your name, you need to
Test test = testMap.get("key");
if (test != null) {
test.setName("new name");
}
If you want to update the Map key also, then you need
Test test = testMap.remove("oldKey");
if (test != null) {
test.setName("newKey");
test.put("newKey", test);
}
Test test = testMap.remove("name");
if(test != null)
test.put("newname",test);
It is not possible with the current implementation of Test you have, since the name is a private field. You can make it public or add getter/setter methods to the Test class (currently the Test class seems to be completely useless unless you haven't missed some code).
After that, you can either replace the desired instance of Test with a new one with necessary fields, or update the name. Code:
public class Test {
public String name;
public String id;
public Object val;
public Test(Object val,String name.String id){
this.val =val;
this.id=id;
this.name = name;
}
}
Map<String, Test> testMap = new HashMap<String, Test();
...
Test test = testMap.get(key);
test.name = newName;
testMap.put(key, test);
// or
Test test2 = testMap.get(key);
testMap.put(key, new Test(test2.val, newName, test2.id));

Categories