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 的实例,您将不会对 namevalue 属性的内部实现或任何注册的侦听器感兴趣在这些财产上;您只会对序列化由属性表示的 感兴趣(即 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": {}
      }
    }
  }
]

注意 StringPropertyIntegerProperty 的内部元素是如何序列化的,包括侦听器,它们几乎肯定与您要保留或传输的数据无关。

在您的异常中,您看到导致异常的侦听器序列化(在某个地方,您似乎在一个或多个属性上注册了绑定或显式弱侦听器:弱侦听器无法序列化)。

更糟糕的是,这无法反序列化:

List<Item> itemsFromGson = gson.fromJson(gsonJson, new TypeToken<List<Item>>() {}.getType());

产生异常,因为StringPropertyIntegerProperty无法构造。

此处的一个解决方案是为 StringPropertyIntegerProperty(以及其他 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 序列化库使用“属性 访问”,即它们使用 getset 方法来序列化和反序列化字段。因此,Jackson 与遵循标准 JavaFX 属性 模式(如上面的 Item class) 的 bean classes 配合得很好,只要属性都是read/write(即它们有相应的getset方法);只读属性需要额外的工作。

我只需要将 Rectangle(在我的 Auto-class 中)作为瞬态变量。 FxGson 可以处理 JavaFX-Properties,但不能处理 Shape 实例。所以我在序列化时忽略了那个字段,并确保我以另一种方式初始化了那个字段。