Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed last year.
Improve this question
If there is a structure like my JSON structure below, how should we create Entity Classes? There are no examples of this. While #embeded was used for inner arrays in the articles written long ago, now a structure like converter is used. Which one should we use? What do these do? How can I create a struct of my type? Please help in Java
All required structures are available here: https://github.com/theoyuncu8/roomdb
JSON Data
{
"MyData": [
{
"food_id": "1",
"food_name": "Food 1",
"food_image": "imageurl",
"food_kcal": "32",
"food_url": "url",
"food_description": "desc",
"carb_percent": "72",
"protein_percent": "23",
"fat_percent": "4",
"units": [
{
"unit": "Unit A",
"amount": "735.00",
"calory": "75.757",
"calcium": "8.580",
"carbohydrt": "63.363",
"cholestrl": "63.0",
"fiber_td": "56.12",
"iron": "13.0474",
"lipid_tot": "13.01",
"potassium": "11.852",
"protein": "717.1925",
"sodium": "112.02",
"vit_a_iu": "110.7692",
"vit_c": "110.744"
},
{
"unit": "Unit C",
"amount": "32.00",
"calory": "23.757",
"calcium": "53.580",
"carbohydrt": "39.363",
"cholestrl": "39.0",
"fiber_td": "93.12",
"iron": "93.0474",
"lipid_tot": "93.01",
"potassium": "9.852",
"protein": "72.1925",
"sodium": "10.0882",
"vit_a_iu": "80.7692",
"vit_c": "80.744"
}
]
},
{
"food_id": "2",
"food_name": "Food 2",
"food_image": "imageurl",
"food_kcal": "50",
"food_url": "url",
"food_description": "desc",
"carb_percent": "25",
"protein_percent": "14",
"fat_percent": "8",
"units": [
{
"unit": "Unit A",
"amount": "25.00",
"calory": "25.757",
"calcium": "55.580",
"carbohydrt": "53.363",
"cholestrl": "53.0",
"fiber_td": "53.12",
"iron": "53.0474",
"lipid_tot": "53.01",
"potassium": "17.852",
"protein": "757.1925",
"sodium": "122.02",
"vit_a_iu": "10.7692",
"vit_c": "10.744"
},
{
"unit": "Unit C",
"amount": "2.00",
"calory": "2.757",
"calcium": "5.580",
"carbohydrt": "3.363",
"cholestrl": "3.0",
"fiber_td": "3.12",
"iron": "3.0474",
"lipid_tot": "3.01",
"potassium": "77.852",
"protein": "77.1925",
"sodium": "12.02",
"vit_a_iu": "0.7692",
"vit_c": "0.744"
},
{
"unit": "Unit G",
"amount": "1.00",
"calory": "2.1",
"calcium": "0.580",
"carbohydrt": "0.363",
"cholestrl": "0.0",
"fiber_td": "0.12",
"iron": "0.0474",
"lipid_tot": "0.01",
"potassium": "5.852",
"protein": "0.1925",
"sodium": "1.02",
"vit_a_iu": "0.7692",
"vit_c": "0.744"
}
]
}
]
}
Entity Class
Foods Class
public class Foods {
#SerializedName("food_id")
#Expose
private String foodId;
#SerializedName("food_name")
#Expose
private String foodName;
#SerializedName("food_image")
#Expose
private String foodImage;
#SerializedName("food_kcal")
#Expose
private String foodKcal;
#SerializedName("food_url")
#Expose
private String foodUrl;
#SerializedName("food_description")
#Expose
private String foodDescription;
#SerializedName("carb_percent")
#Expose
private String carbPercent;
#SerializedName("protein_percent")
#Expose
private String proteinPercent;
#SerializedName("fat_percent")
#Expose
private String fatPercent;
// here
#SerializedName("units")
#Expose
private List<FoodUnitsData> units = null;
// getter setter
}
FoodUnitsData Class
public class FoodUnitsData {
#SerializedName("unit")
#Expose
private String unit;
#SerializedName("amount")
#Expose
private String amount;
#SerializedName("calory")
#Expose
private String calory;
#SerializedName("calcium")
#Expose
private String calcium;
#SerializedName("carbohydrt")
#Expose
private String carbohydrt;
#SerializedName("cholestrl")
#Expose
private String cholestrl;
#SerializedName("fiber_td")
#Expose
private String fiberTd;
#SerializedName("iron")
#Expose
private String iron;
#SerializedName("lipid_tot")
#Expose
private String lipidTot;
#SerializedName("potassium")
#Expose
private String potassium;
#SerializedName("protein")
#Expose
private String protein;
#SerializedName("sodium")
#Expose
private String sodium;
#SerializedName("vit_a_iu")
#Expose
private String vitAIu;
#SerializedName("vit_c")
#Expose
private String vitC;
// getter setter
}
What do these do?
TypeConverters are used to convert a type that room cannot handle to a type that it can (String, primitives, integer types such as Integer, Long, decimal types such as Double, Float).
#Embedded basically says include the member variables of the #Embedded class as columns. e.g. #Embedded FoodUnitsData foodUnitsData;.
Test/Verify the Schema from the Room perspective
With the above class and with the entities defined in the class annotated with #Database (FoodDatabase) it would be a good idea to compile/build the project and fix anything that room complains about (none in this case).
So have FoodDataabse to be :-
#Database(entities = {Foods.class, FoodUnitsDataEntity.class /*<<<<<<<<<< ADDED*/}, version = 1)
public abstract class FoodDatabase extends RoomDatabase {
public abstract DaoAccess daoAccess(); //* do not inlcude this line until the DaoAccess class has been created
}
Note see comment re DaoAccess (i.e. comment out the line)
and then CTRL + F9 and check the build log
Fourth DaoAccess
Obviously FoodUnitsDataEntity rows need to be added, update and deleted. It would also be very convenient if a Foods object could drive adding the FoodUnitsDataEntity rows all in one. This requires a method with a body therefore DaoAccess is changed from an interface to an abstract class to facilitate such a method.
Which one should we use?
You main issue is with the List of FoodUnitsData
Although you could convert the List and use a TypeConverter I would suggest not.
you would probably convert to a JSON string (so you extract from JSON into objects to then store the embedded objects as JSON). You BLOAT the data and also make using that data difficult.
Say for example you wanted to do a search for foods that have 1000 calories or more this would require a pretty complex query or you would load ALL the database and then loop through the foods and then the units.
I would say that #Embedded is the method to use. Along with using #Ignore (the opposite i.e. exclude the member variable from being a column). i.e. you would #Ignore the List in the Foods class.
With #Embedded you can then easily use individual values in queries.
You could then do something like SELECT * FROM the_table_used_for_the_foodunitsdata WHERE calory > 1000 and you would get a List of FoodUnitsData returned. SQLite will do this pretty efficiently.
Working Example
So putting the above into a working example:-
First the Foods class and adding the #Ignore annotation :-
#Entity(tableName = "food_data") // ADDED to make it usable as a Room table
public class Foods {
#SerializedName("food_id")
#Expose
#PrimaryKey // ADDED as MUST have a primary key
#NonNull // ADDED Room does not accept NULLABLE PRIMARY KEY
private String foodId;
#SerializedName("food_name")
#Expose
private String foodName;
#SerializedName("food_image")
#Expose
private String foodImage;
#SerializedName("food_kcal")
#Expose
private String foodKcal;
#SerializedName("food_url")
#Expose
private String foodUrl;
#SerializedName("food_description")
#Expose
private String foodDescription;
#SerializedName("carb_percent")
#Expose
private String carbPercent;
#SerializedName("protein_percent")
#Expose
private String proteinPercent;
#SerializedName("fat_percent")
#Expose
private String fatPercent;
#SerializedName("units")
#Expose
#Ignore // ADDED AS going to be a table
private List<FoodUnitsData> units = null;
#NonNull // ADDED (not reqd)
public String getFoodId() {
return foodId;
}
public void setFoodId(#NonNull /* ADDED #NonNull (not reqd)*/ String foodId) {
this.foodId = foodId;
}
public String getFoodName() {
return foodName;
}
public void setFoodName(String foodName) {
this.foodName = foodName;
}
public String getFoodImage() {
return foodImage;
}
public void setFoodImage(String foodImage) {
this.foodImage = foodImage;
}
public String getFoodKcal() {
return foodKcal;
}
public void setFoodKcal(String foodKcal) {
this.foodKcal = foodKcal;
}
public String getFoodUrl() {
return foodUrl;
}
public void setFoodUrl(String foodUrl) {
this.foodUrl = foodUrl;
}
public String getFoodDescription() {
return foodDescription;
}
public void setFoodDescription(String foodDescription) {
this.foodDescription = foodDescription;
}
public String getCarbPercent() {
return carbPercent;
}
public void setCarbPercent(String carbPercent) {
this.carbPercent = carbPercent;
}
public String getProteinPercent() {
return proteinPercent;
}
public void setProteinPercent(String proteinPercent) {
this.proteinPercent = proteinPercent;
}
public String getFatPercent() {
return fatPercent;
}
public void setFatPercent(String fatPercent) {
this.fatPercent = fatPercent;
}
public List<FoodUnitsData> getUnits() {
return units;
}
public void setUnits(List<FoodUnitsData> units) {
this.units = units;
}
}
The Foods class now has two uses:-
as the class for extracting the JSON (where units will be populated with FoodUnitsData objects accordingly)
as the model for the Room table.
See the comments
Second the FoodUnitsDataEntity class.
This is a new class that will be based upon the FoodUnitsData class but include two important values/columns not catered for by the FoodsUnitsData class, namely:-
a unique identifier that will be the primary key, and
a map/reference for establishing the relationship between a row and it's parent in the Foods table. As this column will be used quite frequently (i.e. it is essential for making the relationship) it makes sense to have an index on the column (speeds up making the relationship (like an index in a book would speed up finding stuff))
as there is a relationship, it is wise to ensure that referential integrity is maintained. That is you don't want orphaned units. As such a Foreign Key constraint is employed (a rule saying that the child must have a parent).
as it will be convenient to build/insert based upon a FoodUnitsData object then a constructor has been added that will create a FoodUnitsDataEnity object from a FoodUnitsData object (plus the all important Foods mapping/referencing/associating value).
So :-
/*
NEW CLASS that:-
Has a Unique ID (Long most efficient) as the primary Key
Has a column to reference/map to the parent FoodUnitsData of the food that owns this
Embeds the FoodUnitsData class
Enforces referential integrity be defining a Foreign Key constraint (optional)
If parent is delete then children are deleted (CASCADE)
If the parent's foodId column is changed then the foodIdMap is updated in the children (CASCADE)
*/
#Entity(
tableName = "food_units",
foreignKeys = {
#ForeignKey(
entity = Foods.class, /* The class (annotated with # Entity) of the owner/parent */
parentColumns = {"foodId"}, /* respective column referenced in the parent (Foods) */
childColumns = {"foodIdMap"}, /* Column in the table that references the parent */
onDelete = CASCADE, /* optional within Foreign key */
onUpdate = CASCADE /* optional with foreign key */
)
}
)
class FoodUnitsDataEntity {
#PrimaryKey
Long foodUnitId = null;
#ColumnInfo(index = true)
String foodIdMap;
#Embedded
FoodUnitsData foodUnitsData;
FoodUnitsDataEntity(){}
FoodUnitsDataEntity(FoodUnitsData fud, String foodId) {
this.foodUnitsData = fud;
this.foodIdMap = foodId;
this.foodUnitId = null;
}
}
Third the FoodUnitsData class
This class is ok as it is. However, for the demo/example constructors were added as per :-
public class FoodUnitsData {
#SerializedName("unit")
#Expose
private String unit;
#SerializedName("amount")
#Expose
private String amount;
#SerializedName("calory")
#Expose
private String calory;
#SerializedName("calcium")
#Expose
private String calcium;
#SerializedName("carbohydrt")
#Expose
private String carbohydrt;
#SerializedName("cholestrl")
#Expose
private String cholestrl;
#SerializedName("fiber_td")
#Expose
private String fiberTd;
#SerializedName("iron")
#Expose
private String iron;
#SerializedName("lipid_tot")
#Expose
private String lipidTot;
#SerializedName("potassium")
#Expose
private String potassium;
#SerializedName("protein")
#Expose
private String protein;
#SerializedName("sodium")
#Expose
private String sodium;
#SerializedName("vit_a_iu")
#Expose
private String vitAIu;
#SerializedName("vit_c")
#Expose
private String vitC;
/* ADDED Constructors */
FoodUnitsData(){}
FoodUnitsData(String unit,
String amount,
String calory,
String calcium,
String cholestrl,
String carbohydrt,
String fiberTd,
String iron,
String lipidTot,
String potassium,
String protein,
String sodium,
String vitAIu,
String vitC
){
this.unit = unit;
this.amount = amount;
this.calory = calory;
this.calcium = calcium;
this.cholestrl = cholestrl;
this.carbohydrt = carbohydrt;
this.fiberTd = fiberTd;
this.iron = iron;
this.lipidTot = lipidTot;
this.potassium = potassium;
this.sodium = sodium;
this.protein = protein;
this.vitAIu = vitAIu;
this.vitC = vitC;
}
/* Finish of ADDED code */
public String getUnit() {
return unit;
}
public void setUnit(String unit) {
this.unit = unit;
}
public String getAmount() {
return amount;
}
public void setAmount(String amount) {
this.amount = amount;
}
public String getCalory() {
return calory;
}
public void setCalory(String calory) {
this.calory = calory;
}
public String getCalcium() {
return calcium;
}
public void setCalcium(String calcium) {
this.calcium = calcium;
}
public String getCarbohydrt() {
return carbohydrt;
}
public void setCarbohydrt(String carbohydrt) {
this.carbohydrt = carbohydrt;
}
public String getCholestrl() {
return cholestrl;
}
public void setCholestrl(String cholestrl) {
this.cholestrl = cholestrl;
}
public String getFiberTd() {
return fiberTd;
}
public void setFiberTd(String fiberTd) {
this.fiberTd = fiberTd;
}
public String getIron() {
return iron;
}
public void setIron(String iron) {
this.iron = iron;
}
public String getLipidTot() {
return lipidTot;
}
public void setLipidTot(String lipidTot) {
this.lipidTot = lipidTot;
}
public String getPotassium() {
return potassium;
}
public void setPotassium(String potassium) {
this.potassium = potassium;
}
public String getProtein() {
return protein;
}
public void setProtein(String protein) {
this.protein = protein;
}
public String getSodium() {
return sodium;
}
public void setSodium(String sodium) {
this.sodium = sodium;
}
public String getVitAIu() {
return vitAIu;
}
public void setVitAIu(String vitAIu) {
this.vitAIu = vitAIu;
}
public String getVitC() {
return vitC;
}
public void setVitC(String vitC) {
this.vitC = vitC;
}
}
Fourth DaoAccess
Obviously inerts/updates/ deletes for the new FoodUnitsDataEntity should be added. However note that existing ones have been changed to not return void but instead long for inserts and int for updates deletes.
inserts return eithr -1 or the rowid (a hidden column that all tables (if using Room) will have that uniquely identifies the inserted row). So if it's -1 then row not inserted (or < 0).
delete and updates return the number of affected (updated/deleted) rows.
It would be beneficial to be able to pass a Food object and insert all the units rows. As this requires a method with a body instead of an interface an abstract class will be used.
So DaoAccess becomes :-
#Dao
public /* CHANGED TO abstract class from interface */ abstract class DaoAccess {
#Query("SELECT * FROM food_data")
abstract List<Foods> getAll();
#Insert(onConflict = OnConflictStrategy.IGNORE)
abstract long insert(Foods task);
#Insert(onConflict = OnConflictStrategy.IGNORE)
abstract long insert(FoodUnitsDataEntity foodUnitsDataEntity);
#Delete
abstract int delete(Foods task);
#Delete
abstract int delete(FoodUnitsDataEntity foodUnitsDataEntity);
#Update
abstract int update(Foods task);
#Update
abstract int update(FoodUnitsDataEntity foodUnitsDataEntity);
#Query("") /* Trick Room to allow the use of #Transaction*/
#Transaction
long insertFoodsWithAllTheFoodUnitsDataEntityChildren(Foods foods) {
long rv = -1;
long fudInsertCount = 0;
if (insert(foods) > 0) {
for(FoodUnitsData fud: foods.getUnits()) {
if (insert(new FoodUnitsDataEntity(fud,foods.getFoodId())) > 0) {
fudInsertCount++;
}
}
if (fudInsertCount != foods.getUnits().size()) {
rv = -(foods.getUnits().size() - fudInsertCount);
} else {
rv = 0;
}
}
return rv;
}
}
Fifth FoodDatabase
Just add the FoodUnitsDataEntity as an entity :-
#Database(entities = {Foods.class, FoodUnitsDataEntity.class /*<<<<<<<<<< ADDED*/}, version = 1)
public abstract class FoodDatabase extends RoomDatabase {
public abstract DaoAccess daoAccess();
}
Sixth testing the above in an Activity MainActivity
This activity will :-
Build a Foods object with some embedded FoodUnitsData.
Save it as a JSON string, extract it from the JSON string (logging
the JSON string)
get an instance of the database.
get an instance of the DaoAccess.
use the insertFoodsWithAllTheFoodUnitsDataEntityChildren method to insert the Foods and the assoctiated/related children.
as per :-
public class MainActivity extends AppCompatActivity {
FoodDatabase fooddb;
DaoAccess foodDao;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
/* Build data to test */
Foods foods = new Foods();
foods.setFoodId("MyFood");
foods.setCarbPercent("10.345");
foods.setFoodDescription("The Food");
foods.setFatPercent("15.234");
foods.setFoodImage("The Food Image");
foods.setFoodKcal("120");
foods.setFoodName("The Food");
foods.setFoodUrl("URL for the Food");
foods.setProteinPercent("16.234");
foods.setUnits(Arrays.asList(
new FoodUnitsData("100","15","1200","11","12","13","14","15","16","17","18","19","20","21"),
new FoodUnitsData("1001","151","12001","11","12","13","14","15","16","17","18","19","20","21"),
new FoodUnitsData("1002","152","12002","11","12","13","14","15","16","17","18","19","20","21")
));
String json = new Gson().toJson(foods);
Log.d("JSONINFO",json);
Foods foodsFromJSON = new Gson().fromJson(json,Foods.class);
fooddb = Room.databaseBuilder(this,FoodDatabase.class,"food.db")
.allowMainThreadQueries()
.build();
foodDao = fooddb.daoAccess();
foodDao.insertFoodsWithAllTheFoodUnitsDataEntityChildren(foodsFromJSON);
}
}
Results after running the App
The log includes :-
D/JSONINFO: {"carb_percent":"10.345","fat_percent":"15.234","food_description":"The Food","food_id":"MyFood","food_image":"The Food Image","food_kcal":"120","food_name":"The Food","food_url":"URL for the Food","protein_percent":"16.234","units":[{"amount":"15","calcium":"11","calory":"1200","carbohydrt":"13","cholestrl":"12","fiber_td":"14","iron":"15","lipid_tot":"16","potassium":"17","protein":"18","sodium":"19","unit":"100","vit_a_iu":"20","vit_c":"21"},{"amount":"151","calcium":"11","calory":"12001","carbohydrt":"13","cholestrl":"12","fiber_td":"14","iron":"15","lipid_tot":"16","potassium":"17","protein":"18","sodium":"19","unit":"1001","vit_a_iu":"20","vit_c":"21"},{"amount":"152","calcium":"11","calory":"12002","carbohydrt":"13","cholestrl":"12","fiber_td":"14","iron":"15","lipid_tot":"16","potassium":"17","protein":"18","sodium":"19","unit":"1002","vit_a_iu":"20","vit_c":"21"}]}
Using App Inspection (Database Inspector) :-
and
Separate them to 2 entities, than create a relation class. This relation class uses FoodListModel as embedded property that has relation to UnitList as List.
Related
Good night for everything !
Hi have one problem with my app based in reddit API, where I call post detail route and the response are one array of data objects, but the primary children refers the post, and the second refers to your comments.
the complete response:
[
{
"kind": "Listing",
"data": {
"modhash": "",
"dist": 1,
"children": [],
"after": null,
"before": null
}
},
{
"kind": "Listing",
"data": {
"modhash": "",
"dist": null,
"children": [],
"after": null,
"before": null
}
}
]
Where the first children is an array de data based in the post, ainda the second children is an array de data based in the post comments. Both have different fields, that is, in the first children it does not have all the fields of the second children for example
what is the best way to work with this type of result, be it in kotlin or java?
the url example https://www.reddit.com/r/funny/comments/mei33b/updated_my_wall_art_to_be_more_relevant.json
Can't you just de-serialize it, below seems to work fine (i haven't bothered adding all fields)?
public static void main(String[] args) {
ObjectMapper mapper = new ObjectMapper();
try (FileInputStream fs = new FileInputStream("reddit.json")) {
Reddit[] reddit = mapper.readValue(fs, Reddit[].class);
System.out.println(reddit[0].kind);
System.out.println(reddit[0].data.children[0].data.thumbnail);
} catch (Exception e) {
e.printStackTrace();
}
}
public static class Reddit {
public final String kind;
public final Data data;
#JsonCreator
public Reddit(
#JsonProperty("kind") String kind,
#JsonProperty("data") Data data
) {
this.kind = kind;
this.data = data;
}
}
#JsonIgnoreProperties(ignoreUnknown = true)
public static class Data {
public final String modhash;
public final Integer dist;
public final ChildData[] children;
public final String after;
public final String before;
#JsonCreator
public Data(
#JsonProperty("modhash") String modhash,
#JsonProperty("dist") Integer dist,
#JsonProperty("children") ChildData[] children,
#JsonProperty("after") String after,
#JsonProperty("before") String before
) {
this.modhash = modhash;
this.dist = dist;
this.children = children;
this.after = after;
this.before = before;
}
}
public static class ChildData {
public final String kind;
public final ChildDataData data;
#JsonCreator
public ChildData(
#JsonProperty("kind") String kind,
#JsonProperty("data") ChildDataData data
) {
this.kind = kind;
this.data = data;
}
}
#JsonIgnoreProperties(ignoreUnknown = true)
public static class ChildDataData {
public final Integer total_awards_received;
public final String body;
public final String thumbnail;
#JsonCreator
public ChildDataData(
#JsonProperty("total_awards_received") Integer total_awards_received,
#JsonProperty("body") String body,
#JsonProperty("thumbnail") String thumbnail
) {
this.total_awards_received = total_awards_received;
this.body = body;
this.thumbnail = thumbnail;
}
}
Something like that, you probably want to change names to something that makes sense to you and you need to add all fields to ChildDataData but i think you get the point?
Gradle dependencies
dependencies {
implementation group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.12.2'
implementation group: 'com.fasterxml.jackson.core', name: 'jackson-annotations', version: '2.12.2'
}
Problem statement
Is that the JSON which is to be deserialize into the below given POJO's
is setting value of credentialType as null when i send the below JSON through postman
{
"credential": [
{
"#type": "mobile",
"credentialName": "cred-2",
"email": "s#s.com"
},
{
"#type": "card",
"credentialNumber": "1"
}
]
}
Expected outcome
What i want to achieve is that with the above JSON the credential type should be set as either MOBILE for MobileCredentialDto or CARD for CardCredentialDto
#Getter
public class SecureDto {
private List<CredentialDto> credential;
#JsonCreator
public HandoutDto(#JsonProperty("credential") final List<CredentialDto> credential) {
this.credential = Collections.unmodifiableList(credential);
}
}
#Getter
public class SecureDto {
private List<CredentialDto> credential;
#JsonCreator
public HandoutDto(#JsonProperty("credential") final List<CredentialDto> credential) {
this.credential = Collections.unmodifiableList(credential);
}
}
#JsonIgnoreProperties(ignoreUnknown = true)
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME)
#JsonSubTypes({
#JsonSubTypes.Type(value = CardCredentialDto.class, name = "card"),
#JsonSubTypes.Type(value = MobileCredentialDto.class, name = "mobile"),
})
#Getter
#Setter
public class CredentialDto {
private CredentialType credentialType;
#JsonCreator
public CredentialDto(#JsonProperty("credentialType") final String credentialType) {
this.credentialType = CredentialType.valueOf(credentialType);
}
public CredentialDto() {
}
public void setCredentialType(final CredentialType credentialType) {
this.credentialType = CredentialType.MOBILE;
}
}
#Getter
#Setter
public class MobileCredentialDto extends CredentialDto {
private String credentialName;
private String email;
public MobileCredentialDto(final String credentialId,
final String state,
final String credentialNumber,
final String credentialName,
final String email) {
super(credentialId, state, credentialNumber, CredentialType.MOBILE.name());
this.credentialName = credentialName;
this.email = email;
}
public MobileCredentialDto() {
}
public String getCredentialName() {
return credentialName;
}
public String getEmail() {
return email;
}
}
#Getter
#Setter
public class CardCredentialDto extends CredentialDto {
public CardCredentialDto(final String credentialId,
final String state,
final String credentialNumber) {
super(credentialId, state, credentialNumber,CredentialType.CARD.name());
}
public CardCredentialDto() {
}
}
public enum CredentialType {
MOBILE("MOBILE"),
CARD("CARD");
private final String name;
CredentialType(final String name) {
this.name = name;
}
}
I Found the answer.
what i did was set the visible = true in JsonTypeInfo.
In short by setting the visible = true allowed the jackson to do the following
Note on visibility of type identifier: by default, deserialization (use during reading of JSON) of type identifier is completely handled by Jackson, and is not passed to deserializers. However, if so desired, it is possible to define property visible = true in which case property will be passed as-is to deserializers (and set via setter or field) on deserialization.
Refer the doc here for more understanding.
https://fasterxml.github.io/jackson-annotations/javadoc/2.4/com/fasterxml/jackson/annotation/JsonTypeInfo.html
Below is the code
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME ,
visible = true,
property = "credentialType")
#JsonSubTypes({
#JsonSubTypes.Type(value = CardCredentialDto.class, name = "card"),
#JsonSubTypes.Type(value = MobileCredentialDto.class, name = "mobile"),
})
#Getter
#Setter
public class CredentialDto {
#JsonProperty(value = "credentialType")
private CredentialType credentialType;
#JsonCreator
public CredentialDto(#JsonProperty("credentialType") final String credentialType) {
this.credentialType = CredentialType.valueOf(credentialType);
}
public CredentialDto() {
}
public void setCredentialType(final CredentialType credentialType) {
this.credentialType = CredentialType.MOBILE;
}
}
and the json will look like this
{
"credential": [
{
"credentialType": "mobile",
"credentialName": "cred-2",
"email": "s#s.com"
},
{
"credentialType": "card",
"credentialNumber": "1"
}
]
}
i have a String returned by a service, in this JSON format:
String message = {
"Tickets":
[{
"Type": "type1",
"Author": "author1",
"Rows":
[
{
"Price": "100.0",
"Date": "24/06/2016",
"Amount": "10"
},
{
"Type": "Comment",
"Value": "some comment goes here"
}
],
"ID": "165"
}],
"Desk": "desk1",
"User": "user1"
}
I need to parse it and convert into a Java object.
I tried to create a dom like this:
public class TicketWrapper{
private Ticket ticket;
private String desk;
private String user;
}
public class Ticket {
private String type;
private String author;
private List<Row> rows;
private String id;
}
public class Row1{
private float price;
private Date date;
private int amount;
}
public class Row2{
private String type;
private float value;
}
Then I try to parse it with Google Gson, this way:
TicketWrapper ticket = gson.fromJson(message, TicketWrapper.class)
but if I print it System.out.println(gson.toJson(ticket)), it prints:
{"desk" : 0, "user" : 0}
I don't know how to parse that Json into a Java Object, and how to tell him that a row into "Rows" can be of the Row1 type or Row2 type.
I think there is a few issues, such names of properties in lower case and dateformat and mix types of rows. I just changed like this and worked for me:
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.annotations.SerializedName;
import org.junit.Test;
import java.util.Date;
import java.util.List;
public class CheckTest {
#Test
public void thisTest() {
Gson gson = new GsonBuilder()
.setDateFormat("dd-MM-yyyy")
.setPrettyPrinting()
.create();
String message = "{\"Tickets\":" +
"[{\"Type\":\"type1\"," +
"\"Author\":\"author1\"," +
"\"Rows\":[{\"Price\":\"100.0\"," +
"\"Date\":\"24-06-2016\"," +
"\"Amount\":\"10\"}," +
"{\"Type\":\"Comment\"," +
"\"Value\":\"some comment goes here\"}]," +
"\"ID\":\"165\"}]," +
"\"Desk\":\"desk1\"," +
"\"User\":\"user1\"}";
TicketWrapper ticket = gson.fromJson(message, TicketWrapper.class);
System.out.println(ticket.toString());
}
public class TicketWrapper {
#SerializedName("Tickets")
private List<Ticket> tickets;
#SerializedName("Desk")
private String desk;
#SerializedName("User")
private String user;
public TicketWrapper() {
}
}
public class Ticket {
#SerializedName("Type")
private String type;
#SerializedName("Author")
private String author;
#SerializedName("Rows")
private List<Row> rows;
#SerializedName("ID")
private String id;
public Ticket() {
}
}
public class Row {
#SerializedName("Type")
private String type;
#SerializedName("Value")
private String value;
#SerializedName("Price")
private float price;
#SerializedName("Date")
private Date date;
#SerializedName("Amount")
private int amount;
public Row() {
}
}
}
As others have already mentioned in the comment, you need to make sure the mapping directly reflects the file names. It needs to be 'User' and 'Desk' instead of 'user' and 'desk'. Also, you have a list of tickets, which would map to List Tickets.
I'm currently consuming a REST API with RetroFit & Jackson. Consider the following response JSON when retrieving users based on a search query when:
One result was found
{
name: "API name",
results: {
count: 1
users: {
username: "username",
age: 15
}
}
}
Multiple results were found
{
name: "API name",
results: {
count: 2,
users: [{
username: "username1",
age: 18
}, {
username: "username2",
age: 19
}]
}
}
As you can see, the users-property contains dynamic JSON: based on the found results, the value of "users" could either be a list of user objects, or 1 user object.
As such, I've designed my Java POJOs using polymorphism, as follows:
public class UserResponse {
#JsonProperty("name")
private String apiName
#JsonProperty("results")
private AResultList resultList
//Getters & Setters
//Constructor
#JsonCreator
public UserResponse(#JsonProperty("name") String name, #JsonProperty("results") AResultList r) {
this.apiName = name;
this.resultList = r;
}
}
#JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.PROPERTY,
property = "classType"
)
#JsonSubTypes({
#JsonSubTypes.Type(value = ResultObject.class, name="ResultObject"),
#JsonSubTypes.Type(value = ResultList.class, name="ResultList")
})
public abstract class AResultList {
#JsonProperty("count)
private long totalCount;
//Getters & Setters
//Constructors
#JsonCreator
public AResultList(#JsonProperty("count) long count) {
this.totalCount = count;
}
}
public class ResultObject extends AResultList {
#JsonProperty("users")
private User user;
//Getters & Setters
//Constructor
#JsonCreator
public ResultObject(#JsonProperty("count) long count, #JsonProperty("users") User u) {
super(count);
this.user = u;
}
}
public class ResultList extends AResultList {
#JsonProperty("users")
private List<User> users;
//Getters & Setters
//Constructor
#JsonCreator
public ResultObject(#JsonProperty("count) long count, #JsonProperty("users") List<User> u) {
super(count);
this.users = u;
}
}
public class User {
#JsonProperty("username")
private String username;
#JsonProperty("age")
private long userAge;
//Getters & Setters
//Constructor
#JsonCreator
public User(#JsonProperty("username") String u, #JsonProperty("age") long a) {
this.userAge = a;
this.username = u;
}
}
For your information: A snippet for instantiating RetroFit
ObjectMapper o = new ObjectMapper();
retrofit = new Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(JacksonConverterFactory.create(o))
.client(okClient)
.build();
Trying to retrieve this information, however, results in the following error:
com.fasterxml.jackson.databind.JsonMappingException: Could not find creator property with name 'count' (in class org.namespace.AResultList)
at [Source: java.io.InputStreamReader#41cd2648; line: 1, column: 1]
I've been stuck on this "dynamic data" problem for 2 days now. Already tried to use GSON library and a lot of other things but to no avail. So I'd like to ask:
- Why is Jackson causing this? Is this a bug? Some users have already asked this question but the provided solutions did not work for my case.
- Is this the correct way of handling data of which part is dynamic?
I've tested my code without the use of polymorphic classes (and only retrieving 1 User from the API) and object maps perfectly. The problem is caused by the polymorphism, but I cannot figure out how to fix it.
The polymorphism configuration you're specifying in the Jackson annotations there is suggesting a {"classType":"ResultObject",...} or {"classType":"ResultList",...} is going to be in the JSON-- which it isn't. I'm not sure of the exact cause of the error you're receiving, but it seems to be looking for the creator on the abstract class since there is not type property.
[For polymorphism, Jackson needs something to read from the JSON to determine what type of bean to deserialize at this point: you don't really have one, just the array/objectness of users. I think therefore that Jackson's polymorphism support isn't a good fit for this situation]
In fact, to allow a property to either take a single item or a list of items, you just need to enable the ACCEPT_SINGLE_VALUE_AS_ARRAY deserialization feature. However, this needs to be enabled globally, there doesn't seem to be a way to target it to a specific property.
public static final class UserResponse {
public String name;
public Results results;
public static final class Results {
public int count;
public List<User> users;
}
public static final class User {
public String username;
public int age;
}
}
#Test
public void reads_single_result() throws Exception {
ObjectReader reader = new ObjectMapper().reader(UserResponse.class)
.with(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY)
.with(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES).with(JsonParser.Feature.ALLOW_SINGLE_QUOTES);
UserResponse response = reader.readValue("{ name: 'API name', results: { count: 1,"
+ " users: { username: 'username', age: 15 } } }");
assertThat(response.results.users, iterableWithSize(1));
assertThat(response.results.users.get(0).username, equalTo("username"));
}
#Test
public void reads_two_results() throws Exception {
ObjectReader reader = new ObjectMapper().reader(UserResponse.class)
.with(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY)
.with(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES).with(JsonParser.Feature.ALLOW_SINGLE_QUOTES);
UserResponse response = reader.readValue("{ name: 'API name', results: { count: 2,"
+ " users: [{ username: 'username1', age: 18 }, { username: 'username2', age: 19 }] } }");
assertThat(response.results.users, iterableWithSize(2));
assertThat(response.results.users.get(0).username, equalTo("username1"));
assertThat(response.results.users.get(1).username, equalTo("username2"));
}
Oh, and if you want to get rid of the useless results object in there, you can do that with a converter:
public static final class UserResponseWithConverter {
public String name;
#JsonProperty("results")
#JsonDeserialize(converter = ConvertResultsToUserList.class)
public List<User> users;
public static final class Results {
public int count;
public List<User> users;
}
public static final class User {
public String username;
public int age;
}
public static final class ConvertResultsToUserList extends StdConverter<Results, List<User>> {
#Override
public List<User> convert(Results value) {
return value.users;
}
}
}
Configuring for correct serialization is left as an exercise for the reader ;)
I have a JSON payload that looks like this:
{
"id": 32,
"name": "[Sample] Tomorrow is today, Red printed scarf",
"primary_image": {
"id": 247,
"zoom_url": "www.site.com/in_123__14581.1393831046.1280.1280.jpg",
"thumbnail_url": "www.site.com/in_123__14581.1393831046.220.290.jpg",
"standard_url": "www.site.com/in_123__14581.1393831046.386.513.jpg",
"tiny_url": "www.site.com/in_123__14581.1393831046.44.58.jpg"
}
}
Can I unwrap a specific field and discard all the others? In other words, can I bind this directly to a POJO like this:
public class Product {
private Integer id;
private String name;
private String standardUrl;
}
There are lots of ways. Do you need to deserialize, serialize or both?
One way to deserialize would be to use a creator method that takes the image as a tree node:
public static class Product {
private Integer id;
private String name;
private String standardUrl;
public Product(#JsonProperty("id") Integer id,
#JsonProperty("name") String name,
#JsonProperty("primary_image") JsonNode primaryImage) {
this.id = id;
this.name = name;
this.standardUrl = primaryImage.path("standard_url").asText();
}
}
The creator doesn't have to be a constructor, you could have a static method that is only used for Jackson deserialization.
You'd have to define a custom serializer to reserialize this, though (e.g. a StdDelegatingSerializer and a converter to wrap the string back up as an ObjectNode)
There are different ways to skin this cat, I hope you can use Jackson 2 for this, since it offers great ways to deserialize Json data, one of my favorites deserialization features is the one I'll show you here (using Builder Pattern) because allows you to validate instances when they are being constructed (or make them immutable!). For you this would look like this:
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import java.util.Map;
#JsonDeserialize(builder = Product.Builder.class)
public class Product {
private Integer id;
private String name;
private String standardUrl;
private Product(Builder builder) {
//Here you can make validations for your new instance.
this.id = builder.id;
this.name = builder.name;
//Here you have access to the primaryImage map in case you want to add new properties later.
this.standardUrl = builder.primaryImage.get("standard_url");
}
#Override
public String toString() {
return String.format("id [%d], name [%s], standardUrl [%s].", id, name, standardUrl);
}
#JsonIgnoreProperties(ignoreUnknown = true)
public static class Builder {
private Integer id;
private String name;
private Map<String, String> primaryImage;
public Builder withId(Integer id) {
this.id = id;
return this;
}
public Builder withName(String name) {
this.name = name;
return this;
}
#JsonProperty("primary_image")
public Builder withPrimaryImage(Map<String, String> primaryImage) {
this.primaryImage = primaryImage;
return this;
}
public Product build() {
return new Product(this);
}
}
}
To test it I created this class:
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;
public class Test {
public static void main(String[] args) {
String serialized = "{" +
" \"id\": 32," +
" \"name\": \"[Sample] Tomorrow is today, Red printed scarf\"," +
" \"primary_image\": {" +
" \"id\": 247," +
" \"zoom_url\": \"www.site.com/in_123__14581.1393831046.1280.1280.jpg\"," +
" \"thumbnail_url\": \"www.site.com/in_123__14581.1393831046.220.290.jpg\"," +
" \"standard_url\": \"www.site.com/in_123__14581.1393831046.386.513.jpg\"," +
" \"tiny_url\": \"www.site.com/in_123__14581.1393831046.44.58.jpg\"" +
" }" +
" }";
ObjectMapper objectMapper = new ObjectMapper();
try {
Product deserialized = objectMapper.readValue(serialized, Product.class);
System.out.print(deserialized.toString());
} catch (IOException e) {
e.printStackTrace();
}
}
The output is (using the override toString() method in Product:
id [32], name [[Sample] Tomorrow is today, Red printed scarf], standardUrl [www.site.com/in_123__14581.1393831046.386.513.jpg].
There are two ways to get the response you required. For both methods, we are going to use JsonView.
Create two types of JsonView:
public interface JViews {
public static class Public { }
public static class Product extends Public { }
}
First method
#JsonView(JViews.Public.class)
public class Product {
private Integer id;
private String name;
#JsonIgnore
private Image primaryImage;
#JsonView(JViews.Product.class)
public String getStandardUrl{
return this.primaryImage.getStandardUrl();
}
}
Second way
Using Jackson's #JsonView and #JsonUnwrapped together.
#JsonView(JViews.Public.class)
public class Product {
private Integer id;
private String name;
#JsonUnwrapped
private Image primaryImage;
}
public class Image {
private String zoomUrl;
#JsonView(JViews.Product.class)
private String standardUrl;
}
#JsonUnwrapped annotation flattens your nested object into Product object. And JsonView is used to filter accessible fields. In this case, only standardUrl field is accessible for Product view, and the result is expected to be:
{
"id": 32,
"name": "[Sample] Tomorrow is today, Red printed scarf",
"standard_url": "url"
}
If you flatten your nested object without using Views, the result will look like:
{
"id": 32,
"name": "[Sample] Tomorrow is today, Red printed scarf",
"id":1,
"standard_url": "url",
"zoom_url":"",
...
}
Jackson provided #JsonUnwrapped annotation.
See below link:
http://jackson.codehaus.org/1.9.9/javadoc/org/codehaus/jackson/annotate/JsonUnwrapped.html