I'm currently following a tutorial to help me learn how JavaFX works and in the tutorial they're building a small app to manage peoples information. The tutorial is also using XML for loading/saving but I do not want to use XML and would like to use JSON. I have a Person
model that uses StringProperty
, IntegerProperty
and ObjectProperty
. My issue is that I'm not exactly sure what the best way to load and save this would be without it saving unnecessary fields and also loading without Gson throwing an error.
import java.time.LocalDate;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
/**
* Model class for a Person.
*
* @author Marco Jakob
*/
public class Person {
private final StringProperty firstName;
private final StringProperty lastName;
private final StringProperty street;
private final IntegerProperty postalCode;
private final StringProperty city;
private final ObjectProperty<LocalDate> birthday;
/**
* Default constructor.
*/
public Person() {
this(null, null);
}
/**
* Constructor with some initial data.
*
* @param firstName
* @param lastName
*/
public Person(String firstName, String lastName) {
this.firstName = new SimpleStringProperty(firstName);
this.lastName = new SimpleStringProperty(lastName);
// Some initial dummy data, just for convenient testing.
this.street = new SimpleStringProperty("some street");
this.postalCode = new SimpleIntegerProperty(1234);
this.city = new SimpleStringProperty("some city");
this.birthday = new SimpleObjectProperty<LocalDate>(LocalDate.of(1999, 2, 21));
}
public String getFirstName() {
return firstName.get();
}
public void setFirstName(String firstName) {
this.firstName.set(firstName);
}
public StringProperty firstNameProperty() {
return firstName;
}
public String getLastName() {
return lastName.get();
}
public void setLastName(String lastName) {
this.lastName.set(lastName);
}
public StringProperty lastNameProperty() {
return lastName;
}
public String getStreet() {
return street.get();
}
public void setStreet(String street) {
this.street.set(street);
}
public StringProperty streetProperty() {
return street;
}
public int getPostalCode() {
return postalCode.get();
}
public void setPostalCode(int postalCode) {
this.postalCode.set(postalCode);
}
public IntegerProperty postalCodeProperty() {
return postalCode;
}
public String getCity() {
return city.get();
}
public void setCity(String city) {
this.city.set(city);
}
public StringProperty cityProperty() {
return city;
}
public LocalDate getBirthday() {
return birthday.get();
}
public void setBirthday(LocalDate birthday) {
this.birthday.set(birthday);
}
public ObjectProperty<LocalDate> birthdayProperty() {
return birthday;
}
}
personData
is an ObservableList
of Person
stry (Writer writer = new FileWriter(file)) {
new GsonBuilder().setPrettyPrinting().disableHtmlEscaping().create().toJson(personData, writer);
}
This way of saving currently produces a save with a lot of unnecessary fields like name
, value
, etc when it could be "firstName": "Hans"
[{
"firstName": {
"name": "",
"value": "Hans",
"valid": true,
"helper": {
"observable": {}
}
},
"lastName": {
"name": "",
"value": "Muster",
"valid": true,
"helper": {
"observable": {}
}
},
"street": {
"name": "",
"value": "some street",
"valid": true
},
"postalCode": {
"name": "",
"value": 1234,
"valid": true
},
"city": {
"name": "",
"value": "some city",
"valid": true
},
"birthday": {}
}]
Now when even trying to load the string above with Gson it produces an error, Failed to invoke public javafx.beans.property.StringProperty() with no args
.
Person[] persons;
try (Reader reader = new FileReader(file)) {
persons = gson.fromJson(reader, Person[].class);
}
personData.clear();
personData.addAll(persons);
I've Googled to see if it was possible to use getters and setters with Gson but it doesn't really seem possible so I'm stuck on what to do.
I have faced the same issue with GSON and JavaFX Property Model.And I have resolved it using LinkedHashMap like the following :-
in your model class :-
public Person(LinkedHashMap<String, Object> personData) {
this.firstName = new SimpleStringProperty((String) personData.get("firstName"));
this.lastName = new SimpleStringProperty((String) personData.get("lastName"));
this.street = new SimpleStringProperty((String) personData.get("street"));
this.postalCode = new SimpleIntegerProperty(((Double) personData.get("postalCode")).intValue());
this.city = new SimpleStringProperty((String) personData.get("city"));
String birthdayString = (String) personData.get("birthday");
LocalDate date = LocalDate.parse(birthdayString ,DateTimeFormatter.ofPattern("yyy, mm, dd"));
this.birthday = new SimpleObjectProperty<LocalDate>(date);
}
public LinkedHashMap<String, Object> getPersonData() {
LinkedHashMap<String, Object> personData = new LinkedHashMap<>();
personData.put("firstName", firstName.getValue());
personData.put("lastName", lastName.getValue());
personData.put("street", street.getValue());
personData.put("postalCode", postalCode.getValue());
personData.put("city", city.getValue());
personData.put("birthday", birthday.getValue());
return personData;
}
Then in loader :-
Gson gson = new Gson();
List<LinkedHashMap<String, Object>> persons = new Gson().fromJson(jsonData, new TypeToken<List<LinkedHashMap<String, Object>>>() {}.getType());
for(LinkedHashMap<String, Object> personData : persons) {
Person person = new Person(personData);
}
And to Convert to Json :-
LinkedHashMap<String, Object> personData = person.getPersonData();
String jsonData = new Gson().toJson(personData);
Notice that GSON maps int value to double as it is more generic so you need to cast the postal code to double first and then get the int value from it refer to this question for more info.
How to prevent Gson from expressing integers as floats
I know I'm a bit late to the party, but this is for future readers.
I had the exact same problem. I ended up writing a bunch of Gson TypeAdapter
s, one for each JavaFX property type (and a couple more for Color
and Font
).
I gathered them all in a lightweight library called FxGson (< 30kB).
Now, simply by using FxGson's GsonBuilder
, the JavaFX POJOs will be serialized as if their properties were simple values.
Using the Person
class in your example:
Person p = new Person("Hans", "Muster");
Gson gson = FxGson.coreBuilder().setPrettyPrinting().disableHtmlEscaping().create();
System.out.println(gson.toJson(p));
This outputs:
{
"firstName": "Hans",
"lastName": "Muster",
"street": "some street",
"postalCode": 1234,
"city": "some city",
"birthday": {
"year": 1999,
"month": 2,
"day": 21
}
}
Are GSON a requirement?
I have the same problem with GSON and switched to Jackson. It works:
ObjectMapper mapper = new ObjectMapper();
System.out.println(mapper.writeValueAsString(persons));
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With