jeudi 12 mars 2020

Java Gson - Json serializers and deserializers using design patterns?

thank you in advance for stepping by,

I am currently working on desktop music player app. I have decided to store all necessary info about songs, music playlists etc. inside json file.

I've got this class Library, which is responsible generally speaking for preparing songs and handling json file.

public class Library {

    private static File libFile;
    private static SongList songList;
    private static List<Album> albumList;
    private static List<Artist> artistList;
    private static List<Playlist> playlistList;
    ...
}

I need to serialize those 4 classes which are contained inside Library:

public class SongList {
    protected SimpleStringProperty title;
    protected List<Song> songs;
    ...
}

public class Album extends SongList implements Comparable<Album> {

    private SimpleStringProperty artist;
    private int releaseYear;
    private long lengthInSeconds;
    private Image cover;
    private SimpleObjectProperty<Image> coverProperty;
    private int id;
    ...
}

public class Playlist extends SongList implements Comparable<Playlist>{ 
    ... 
}

public class Artist implements Comparable<Artist>{

    private SimpleStringProperty name;
    private List<Album> albums;
    ...
}

(* NOTE ) for serialization of SongList i use default serialization using fx-gson package, so Property objects are not so verbose.

Wanted json file structure:

{
  "songlist": {
    "title": "main.songlist",
    "songs": [
      {
        "title": "songtitle",
        "artist": "artistname",
        "album": "albumname",
        "length": "4:09",
        "lengthInSeconds": 249,
        "playCount": 0,
        "directory": "directory",
        "id": 0
      },
      {
        "title": "songtitle",
        "artist": "artistname",
        "album": "albumname",
        "length": "4:09",
        "lengthInSeconds": 249,
        "playCount": 0,
        "directory": "directory",
        "id": 1
      }
    ]
  },
  "playlistList": {
    "playlist1": [
      0,
      1
    ],
    "playlist2": [
      0,
      1
    ]
  },
  "albums": {
    "Album name": {
      "id": 10,
      "artist": "artist",
      "release.year": 2019,
      "length.in.seconds": 786,
      "songs": [
        0,
        1
      ]
    }
  },
  "artistList": {
    "artist name1":[
      10
    ]
  }
}

There are 2 main methods responsible for handling file: Library.load() and Library.save(). For example, save method uses couple methods responsible for saving albums, playlists or artists collections like this one:

private static <T extends SongList> JsonElement parseSongList(List<T> list) {

    GsonBuilder customGsonBuilder = new GsonBuilder();

    //Custom serializer for songlist (id only)
    JsonSerializer <SongList> playlistSerializer = new JsonSerializer<SongList>(){
        @Override
        public JsonElement serialize(SongList src, Type type, JsonSerializationContext context) {
            JsonArray ids = new JsonArray();
            for(Song song : src.get()) {
                ids.add(song.getId());
            }
            return ids;
        };
    };


    //Custom serializer for albums
    JsonSerializer <Album> albumSerializer = new JsonSerializer<Album>() {
        @Override
        public JsonElement serialize(Album src, Type type, JsonSerializationContext context) {
            JsonObject result = new JsonObject();
            result.addProperty("id", src.getId());
            result.addProperty("artist", src.getArtist().get());
            result.addProperty("release.year", src.getReleaseYear());
            result.addProperty("length.in.seconds", src.getLengthInSeconds());

            JsonArray ids = (JsonArray) playlistSerializer.serialize(src, type, context);
            result.add("songs", ids);
            return result;
        }
    };

    //Selecting type adapter
    if(!list.isEmpty()) {
        SongList tmp = list.get(0);
        if(tmp instanceof Playlist) {
            customGsonBuilder.registerTypeAdapter(Playlist.class, playlistSerializer);
        }else if(tmp instanceof Album) {
            customGsonBuilder.registerTypeAdapter(Album.class, albumSerializer);
        }
    }else {
        return null;
    }

    Gson customGson = customGsonBuilder.setPrettyPrinting().create();

    //serializing
    JsonObject songs = new JsonObject();
    for(SongList songlist : list) {
        songs.add(songlist.getTitle(), customGson.toJsonTree(songlist));
    }

    return songs;

}

As you can see, it's messy (I think), and it's not even the messiest one. Each method used by load/save provides different serializers and deserializers for differenct structures.

I'd like to do "general clean up" in Library class, so it's easy to read, maintain, and ensure that if needed, i can add more music structures or functionalities easily.

Here are my questions:

1) Each Songlist, Playlist, Album or Artist object comes down to LinkedList<Songlist> - is there any preferred way for serializing and deserializing those structures or it doesn't really matter? Should i provide serializers and deserializers for Songlist/Album/Artist or for List<Songlist> ?

2) I want to contain all objects, methods, classes etc. responsible directly for serializing/deserializing in one module (class/package or sth else). I've never used one, but is there any design pattern that would be suitable for my app (factory method?). If yes, I would be really grateful for providing me some general plan ( or guide link )I should follow to implement one.

3) Any overall tips about the Library structures?

Any other suggestions about code/project structures are welcome too, there are probably many issues.

If i have missed any important details, i will provide them asap.

Aucun commentaire:

Enregistrer un commentaire