GSON 序列化因自定义对象而失败
GSON serialization fails with custom Objects
我有以下class
public class Strassennetz {
private ObservableMap<Position, Strassenabschnitt> abschnitte;
private Map<Position, List<Auto>> autos;
private SimpleListProperty<Auto> autoList;
private BooleanProperty simuliert;
private String name;
public static Strassennetz instance;
...
}
我想用GSON/FxGson序列化和反序列化:
Gson gsonBuilder = FxGson.coreBuilder()
.registerTypeAdapter(Strassenabschnitt.class, StrassenAdapter.getInstance())
.enableComplexMapKeySerialization()
.setPrettyPrinting()
.create();
String jsonResult = gsonBuilder.toJson(instance);
StrassenAdapter 是正确(反)序列化摘要 class Strassenabschnitt 所必需的。
当我设置字段“autos”和“autoList”瞬态时,该序列化按预期工作。
一旦我想在序列化中包含这些字段(这非常重要),就会出现以下异常:
Exception in thread "JavaFX Application Thread"
java.lang.IllegalArgumentException: class
com.sun.javafx.util.WeakReferenceQueue$ListEntry declares multiple
JSON fields named next
class Auto 看起来像这样:
public class Auto {
public enum AutoModell {ROT, POLIZEI, BLAU}
private int geschwindigkeit;
private static final int MAXGESCHWINDIGKEIT = 8;
private SimpleObjectProperty<Himmelsrichtung> richtung = new SimpleObjectProperty<>();
private Queue<Wendepunkt> wendepunkte;
private SimpleIntegerProperty positionX;
private SimpleIntegerProperty positionY;
private int breite;
private int laenge;
private AutoModell autoModell;
private final transient Strassennetz strassennetz;
private Rectangle rectangle;
...
}
我浏览了三个 google 个搜索结果页面来寻找答案,但我没有找到答案。
GSON 确实不能很好地处理 JavaFX 属性,因为它没有正确地尊重封装。 GSON 序列化和对象的默认方式是使用反射递归地获取 fields 的值,而不是获取 properties 的值(由 get/set 方法)。
在 JavaFX 应用程序中,JavaFX 属性通常用在数据模型中以实现“增强的 java beans”(其中增强是使用属性注册侦听器的能力等)
考虑一个典型的 JavaFX bean 类型 class:
public class Item {
private final StringProperty name = new SimpleStringProperty();
private final IntegerProperty value = new SimpleIntegerProperty();
public StringProperty nameProperty() {
return name ;
}
public final String getName() {
return nameProperty().get();
}
public final void setName(String name) {
nameProperty().set(name);
}
public IntegerProperty valueProperty() {
return value ;
}
public final int getValue() {
return valueProperty().get() ;
}
public final void setValue(int value) {
valueProperty().set(value);
}
}
如果您想象“手动”序列化此 class 的实例,您将不会对 name
和 value
属性的内部实现或任何注册的侦听器感兴趣在这些财产上;您只会对序列化由属性表示的 值 感兴趣(即 getName()
和 getValue()
返回的值)。要反序列化一个 Item
实例,您只需实例化一个 Item
,然后使用序列化值调用 setName()
和 setValue()
。
如果您尝试“按原样”使用 GSON 来序列化,比如说,这样的 Item
个实例的列表:
public class App {
public static void main(String[] args) throws Exception {
Random rng = new Random();
rng.setSeed(42);
List<Item> items = new ArrayList<>();
for (int i = 1 ; i <= 5 ; i++) {
Item item = new Item();
item.setName("Item "+i);
item.setValue(rng.nextInt(100));
item.valueProperty().addListener((obs, oldValue, newValue) -> System.out.println(newValue));
items.add(item);
}
Gson gson = new GsonBuilder().setPrettyPrinting().create();
String gsonJson = gson.toJson(items);
System.out.println(gsonJson);
}
}
您得到以下内容:
[
{
"name": {
"name": "",
"value": "Item 1",
"valid": false
},
"value": {
"name": "",
"value": 30,
"valid": true,
"helper": {
"observable": {}
}
}
},
{
"name": {
"name": "",
"value": "Item 2",
"valid": false
},
"value": {
"name": "",
"value": 63,
"valid": true,
"helper": {
"observable": {}
}
}
},
{
"name": {
"name": "",
"value": "Item 3",
"valid": false
},
"value": {
"name": "",
"value": 48,
"valid": true,
"helper": {
"observable": {}
}
}
},
{
"name": {
"name": "",
"value": "Item 4",
"valid": false
},
"value": {
"name": "",
"value": 84,
"valid": true,
"helper": {
"observable": {}
}
}
},
{
"name": {
"name": "",
"value": "Item 5",
"valid": false
},
"value": {
"name": "",
"value": 70,
"valid": true,
"helper": {
"observable": {}
}
}
}
]
注意 StringProperty
和 IntegerProperty
的内部元素是如何序列化的,包括侦听器,它们几乎肯定与您要保留或传输的数据无关。
在您的异常中,您看到导致异常的侦听器序列化(在某个地方,您似乎在一个或多个属性上注册了绑定或显式弱侦听器:弱侦听器无法序列化)。
更糟糕的是,这无法反序列化:
List<Item> itemsFromGson = gson.fromJson(gsonJson, new TypeToken<List<Item>>() {}.getType());
产生异常,因为StringProperty
和IntegerProperty
无法构造。
此处的一个解决方案是为 StringProperty
和 IntegerProperty
(以及其他 Property
)classes 定义自定义序列化器和反序列化器,它们简单地序列化和反序列化包含值:
public class App {
public static void main(String[] args) throws Exception {
Random rng = new Random();
rng.setSeed(42);
List<Item> items = new ArrayList<>();
for (int i = 1 ; i <= 5 ; i++) {
Item item = new Item();
item.setName("Item "+i);
item.valueProperty().set(rng.nextInt(100));
item.valueProperty().addListener((obs, oldValue, newValue) -> System.out.println(newValue));
items.add(item);
}
GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.registerTypeAdapter(StringProperty.class, new JsonSerializer<StringProperty>() {
@Override
public JsonElement serialize(StringProperty src, Type typeOfSrc, JsonSerializationContext context) {
return new JsonPrimitive(src.get());
}
});
gsonBuilder.registerTypeAdapter(StringProperty.class, new JsonDeserializer<StringProperty>() {
@Override
public StringProperty deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) {
return new SimpleStringProperty(json.getAsJsonPrimitive().getAsString());
}
});
gsonBuilder.registerTypeAdapter(IntegerProperty.class, new JsonSerializer<IntegerProperty>() {
@Override
public JsonElement serialize(IntegerProperty src, Type typeOfSrc, JsonSerializationContext context) {
return new JsonPrimitive(src.get());
}
});
gsonBuilder.registerTypeAdapter(IntegerProperty.class, new JsonDeserializer<IntegerProperty>() {
@Override
public IntegerProperty deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) {
return new SimpleIntegerProperty(json.getAsJsonPrimitive().getAsInt());
}
});
Gson gson = gsonBuilder.setPrettyPrinting().create();
String gsonJson = gson.toJson(items);
System.out.println(gsonJson);
System.out.println("\n================\n");
List<Item> itemsFromGson = gson.fromJson(gsonJson, new TypeToken<List<Item>>() {}.getType());
System.out.println(itemsFromGson);
}
}
此版本生成预期的
[
{
"name": "Item 1",
"value": 30
},
{
"name": "Item 2",
"value": 63
},
{
"name": "Item 3",
"value": 48
},
{
"name": "Item 4",
"value": 84
},
{
"name": "Item 5",
"value": 70
}
]
可能值得注意的是,默认情况下,Jackson 序列化库使用“属性 访问”,即它们使用 get
和 set
方法来序列化和反序列化字段。因此,Jackson 与遵循标准 JavaFX 属性 模式(如上面的 Item
class) 的 bean classes 配合得很好,只要属性都是read/write(即它们有相应的get
和set
方法);只读属性需要额外的工作。
我只需要将 Rectangle(在我的 Auto-class 中)作为瞬态变量。 FxGson 可以处理 JavaFX-Properties,但不能处理 Shape 实例。所以我在序列化时忽略了那个字段,并确保我以另一种方式初始化了那个字段。
我有以下class
public class Strassennetz {
private ObservableMap<Position, Strassenabschnitt> abschnitte;
private Map<Position, List<Auto>> autos;
private SimpleListProperty<Auto> autoList;
private BooleanProperty simuliert;
private String name;
public static Strassennetz instance;
...
}
我想用GSON/FxGson序列化和反序列化:
Gson gsonBuilder = FxGson.coreBuilder()
.registerTypeAdapter(Strassenabschnitt.class, StrassenAdapter.getInstance())
.enableComplexMapKeySerialization()
.setPrettyPrinting()
.create();
String jsonResult = gsonBuilder.toJson(instance);
StrassenAdapter 是正确(反)序列化摘要 class Strassenabschnitt 所必需的。 当我设置字段“autos”和“autoList”瞬态时,该序列化按预期工作。 一旦我想在序列化中包含这些字段(这非常重要),就会出现以下异常:
Exception in thread "JavaFX Application Thread" java.lang.IllegalArgumentException: class com.sun.javafx.util.WeakReferenceQueue$ListEntry declares multiple JSON fields named next
class Auto 看起来像这样:
public class Auto {
public enum AutoModell {ROT, POLIZEI, BLAU}
private int geschwindigkeit;
private static final int MAXGESCHWINDIGKEIT = 8;
private SimpleObjectProperty<Himmelsrichtung> richtung = new SimpleObjectProperty<>();
private Queue<Wendepunkt> wendepunkte;
private SimpleIntegerProperty positionX;
private SimpleIntegerProperty positionY;
private int breite;
private int laenge;
private AutoModell autoModell;
private final transient Strassennetz strassennetz;
private Rectangle rectangle;
...
}
我浏览了三个 google 个搜索结果页面来寻找答案,但我没有找到答案。
GSON 确实不能很好地处理 JavaFX 属性,因为它没有正确地尊重封装。 GSON 序列化和对象的默认方式是使用反射递归地获取 fields 的值,而不是获取 properties 的值(由 get/set 方法)。
在 JavaFX 应用程序中,JavaFX 属性通常用在数据模型中以实现“增强的 java beans”(其中增强是使用属性注册侦听器的能力等)
考虑一个典型的 JavaFX bean 类型 class:
public class Item {
private final StringProperty name = new SimpleStringProperty();
private final IntegerProperty value = new SimpleIntegerProperty();
public StringProperty nameProperty() {
return name ;
}
public final String getName() {
return nameProperty().get();
}
public final void setName(String name) {
nameProperty().set(name);
}
public IntegerProperty valueProperty() {
return value ;
}
public final int getValue() {
return valueProperty().get() ;
}
public final void setValue(int value) {
valueProperty().set(value);
}
}
如果您想象“手动”序列化此 class 的实例,您将不会对 name
和 value
属性的内部实现或任何注册的侦听器感兴趣在这些财产上;您只会对序列化由属性表示的 值 感兴趣(即 getName()
和 getValue()
返回的值)。要反序列化一个 Item
实例,您只需实例化一个 Item
,然后使用序列化值调用 setName()
和 setValue()
。
如果您尝试“按原样”使用 GSON 来序列化,比如说,这样的 Item
个实例的列表:
public class App {
public static void main(String[] args) throws Exception {
Random rng = new Random();
rng.setSeed(42);
List<Item> items = new ArrayList<>();
for (int i = 1 ; i <= 5 ; i++) {
Item item = new Item();
item.setName("Item "+i);
item.setValue(rng.nextInt(100));
item.valueProperty().addListener((obs, oldValue, newValue) -> System.out.println(newValue));
items.add(item);
}
Gson gson = new GsonBuilder().setPrettyPrinting().create();
String gsonJson = gson.toJson(items);
System.out.println(gsonJson);
}
}
您得到以下内容:
[
{
"name": {
"name": "",
"value": "Item 1",
"valid": false
},
"value": {
"name": "",
"value": 30,
"valid": true,
"helper": {
"observable": {}
}
}
},
{
"name": {
"name": "",
"value": "Item 2",
"valid": false
},
"value": {
"name": "",
"value": 63,
"valid": true,
"helper": {
"observable": {}
}
}
},
{
"name": {
"name": "",
"value": "Item 3",
"valid": false
},
"value": {
"name": "",
"value": 48,
"valid": true,
"helper": {
"observable": {}
}
}
},
{
"name": {
"name": "",
"value": "Item 4",
"valid": false
},
"value": {
"name": "",
"value": 84,
"valid": true,
"helper": {
"observable": {}
}
}
},
{
"name": {
"name": "",
"value": "Item 5",
"valid": false
},
"value": {
"name": "",
"value": 70,
"valid": true,
"helper": {
"observable": {}
}
}
}
]
注意 StringProperty
和 IntegerProperty
的内部元素是如何序列化的,包括侦听器,它们几乎肯定与您要保留或传输的数据无关。
在您的异常中,您看到导致异常的侦听器序列化(在某个地方,您似乎在一个或多个属性上注册了绑定或显式弱侦听器:弱侦听器无法序列化)。
更糟糕的是,这无法反序列化:
List<Item> itemsFromGson = gson.fromJson(gsonJson, new TypeToken<List<Item>>() {}.getType());
产生异常,因为StringProperty
和IntegerProperty
无法构造。
此处的一个解决方案是为 StringProperty
和 IntegerProperty
(以及其他 Property
)classes 定义自定义序列化器和反序列化器,它们简单地序列化和反序列化包含值:
public class App {
public static void main(String[] args) throws Exception {
Random rng = new Random();
rng.setSeed(42);
List<Item> items = new ArrayList<>();
for (int i = 1 ; i <= 5 ; i++) {
Item item = new Item();
item.setName("Item "+i);
item.valueProperty().set(rng.nextInt(100));
item.valueProperty().addListener((obs, oldValue, newValue) -> System.out.println(newValue));
items.add(item);
}
GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.registerTypeAdapter(StringProperty.class, new JsonSerializer<StringProperty>() {
@Override
public JsonElement serialize(StringProperty src, Type typeOfSrc, JsonSerializationContext context) {
return new JsonPrimitive(src.get());
}
});
gsonBuilder.registerTypeAdapter(StringProperty.class, new JsonDeserializer<StringProperty>() {
@Override
public StringProperty deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) {
return new SimpleStringProperty(json.getAsJsonPrimitive().getAsString());
}
});
gsonBuilder.registerTypeAdapter(IntegerProperty.class, new JsonSerializer<IntegerProperty>() {
@Override
public JsonElement serialize(IntegerProperty src, Type typeOfSrc, JsonSerializationContext context) {
return new JsonPrimitive(src.get());
}
});
gsonBuilder.registerTypeAdapter(IntegerProperty.class, new JsonDeserializer<IntegerProperty>() {
@Override
public IntegerProperty deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) {
return new SimpleIntegerProperty(json.getAsJsonPrimitive().getAsInt());
}
});
Gson gson = gsonBuilder.setPrettyPrinting().create();
String gsonJson = gson.toJson(items);
System.out.println(gsonJson);
System.out.println("\n================\n");
List<Item> itemsFromGson = gson.fromJson(gsonJson, new TypeToken<List<Item>>() {}.getType());
System.out.println(itemsFromGson);
}
}
此版本生成预期的
[
{
"name": "Item 1",
"value": 30
},
{
"name": "Item 2",
"value": 63
},
{
"name": "Item 3",
"value": 48
},
{
"name": "Item 4",
"value": 84
},
{
"name": "Item 5",
"value": 70
}
]
可能值得注意的是,默认情况下,Jackson 序列化库使用“属性 访问”,即它们使用 get
和 set
方法来序列化和反序列化字段。因此,Jackson 与遵循标准 JavaFX 属性 模式(如上面的 Item
class) 的 bean classes 配合得很好,只要属性都是read/write(即它们有相应的get
和set
方法);只读属性需要额外的工作。
我只需要将 Rectangle(在我的 Auto-class 中)作为瞬态变量。 FxGson 可以处理 JavaFX-Properties,但不能处理 Shape 实例。所以我在序列化时忽略了那个字段,并确保我以另一种方式初始化了那个字段。