Reputation: 10825
I have a strange issue where I have a HashMap that I serialize for later use on the disk.
It is a HashMap<Path, ConverterMetadata>
the ConverterMetadata
being a custom class that I wrote to keep track of music file metadata.
The ConverterMetadata appears to have proper tags and in my testing I have confirmed that Jackson can write and read Map<Path, String>
instances, so I'm not entirely sure what is happening here, and why it says that it is breaking on the key (Path) object.
Here is the exception, the class, the JSON outputted, and the method that reads/writes it:
The Exception:
Exception in thread "main" com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot find a (Map) Key deserializer for type [simple type, class java.nio.file.Path]
at [Source: (File); line: 1, column: 1]
at com.fasterxml.jackson.databind.exc.InvalidDefinitionException.from(InvalidDefinitionException.java:67)
at com.fasterxml.jackson.databind.DeserializationContext.reportBadDefinition(DeserializationContext.java:1452)
at com.fasterxml.jackson.databind.deser.DeserializerCache._handleUnknownKeyDeserializer(DeserializerCache.java:599)
at com.fasterxml.jackson.databind.deser.DeserializerCache.findKeyDeserializer(DeserializerCache.java:168)
at com.fasterxml.jackson.databind.DeserializationContext.findKeyDeserializer(DeserializationContext.java:500)
at com.fasterxml.jackson.databind.deser.std.MapDeserializer.createContextual(MapDeserializer.java:248)
at com.fasterxml.jackson.databind.DeserializationContext.handleSecondaryContextualization(DeserializationContext.java:682)
at com.fasterxml.jackson.databind.DeserializationContext.findRootValueDeserializer(DeserializationContext.java:482)
at com.fasterxml.jackson.databind.ObjectMapper._findRootDeserializer(ObjectMapper.java:4191)
at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4010)
at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:2922)
at com.protonmail.sarahszabo.stellar.conversions.SpaceBridge.initBridge(SpaceBridge.java:151)
at com.protonmail.sarahszabo.stellar.StellarMode$2.start(StellarMode.java:87)
at com.protonmail.sarahszabo.stellar.Main.stellarConversion(Main.java:203)
at com.protonmail.sarahszabo.stellar.Main.main(Main.java:77)
ConverterMetadata Class:
/**
* A representation of .opus metadata. Used in concordance with a
* {@link StellarOPUSConverter}. All fields are immutable.
*/
public final class ConverterMetadata {
/**
* The default metadata instance.
*/
public static final ConverterMetadata DEFAULT_METADATA = new ConverterMetadata("Unknown Artist",
"Unknown Title", Main.FULL_PROGRAM_NAME, LocalDate.MAX, StellarGravitonField.newPath(""), Integer.MAX_VALUE);
@JsonProperty
private final String artist;
@JsonProperty
private final String title;
@JsonProperty
private final String createdBy;
@JsonProperty
private final LocalDate stellarIndexDate;
@JsonProperty
private final Path albumArtPath;
@JsonProperty
private final int bitrate;
/**
* Constructs a new {@link ConverterMetadata} with the specified arguments.
*
*
* @param artist The artist for this track
* @param title The title of this track
* @param createdBy The program that created this track/last modified this
* track
* @param date The date this track was created
* @param albumArtPath The path to the album art
* @param bitrate The bitrate of the track
*/
@JsonCreator
public ConverterMetadata(@JsonProperty(value = "artist") String artist,
@JsonProperty(value = "title") String title, @JsonProperty(value = "createdBy") String createdBy,
@JsonProperty(value = "stellarIndexDate") LocalDate date, @JsonProperty(value = "albumArtPath") Path albumArtPath,
@JsonProperty(value = "bitrate") int bitrate) {
//Do Consructor Stuff Here
}
}
Code that Writes/Reads from the Ledger File AKA initBridge():
Map<Path, ConverterMetadata> LIBRARY_LEDGER = new HashMap<>();
//Earlier in the code, write ledger, to disk
MAPPER.writeValue(LIBRARY_LEDGER_PATH.toFile(), LIBRARY_LEDGER);
//Later we read the ledger
Map<Path, ConverterMetadata> previousLedger = MAPPER.readValue(LIBRARY_LEDGER_PATH.toFile(),
new TypeReference<HashMap<Path, ConverterMetadata>>() {
});
LIBRARY_LEDGER.putAll(previousLedger);
JSON in the File:
{"/home/sarah/Music/Indexing/Playlists/Best Playlist/Spiral.opus":{"artist":"Vangelis","title":"Spiral","createdBy":"Stellar OPUS Conversion Library 1.4α","stellarIndexDate":[2018,7,23],"albumArtPath":"file:///tmp/Stellar%20OPUS%20Converter%20Temporary%20Directory15723231348656772389/ReIndexing/Spiral.png","bitrate":320},"/home/sarah/Music/Indexing/Playlists/Finished/Aphelion.opus":{"artist":"Scandroid","title":"Aphelion","createdBy":"Stellar OPUS Conversion Library 1.4α","stellarIndexDate":[2018,8,8],"albumArtPath":"file:///tmp/Stellar%20OPUS%20Converter%20Temporary%20Directory15723231348656772389/ReIndexing/Aphelion.png","bitrate":320}
POM:
<properties>
...
<!-- Use the latest version whenever possible. -->
<jackson.version>2.9.8</jackson.version>
...
</properties>
<dependencies>
...
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${jackson.version}</version>
</dependency>
...
</dependencies>
Upvotes: 0
Views: 6160
Reputation: 38710
You need to implement key deserialiser for java.nio.file.Path
class. It could like below:
class PathKeyDeserializer extends KeyDeserializer {
@Override
public Object deserializeKey(String key, DeserializationContext ctxt) {
return Paths.get(key);
}
}
You can register it and use like in below example:
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.KeyDeserializer;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.module.SimpleModule;
import java.io.File;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;
public class JsonApp {
public static void main(String[] args) throws Exception {
Map<Path, String> path2String = new HashMap<>();
path2String.put(Paths.get("user", "downloads"), "Downloads");
path2String.put(Paths.get("home", "des"), "Desktop");
SimpleModule nioModule = new SimpleModule();
nioModule.addKeyDeserializer(Path.class, new PathKeyDeserializer());
ObjectMapper mapper = new ObjectMapper();
mapper.enable(SerializationFeature.INDENT_OUTPUT);
mapper.registerModule(nioModule);
String json = mapper.writeValueAsString(path2String);
System.out.println(json);
path2String = mapper.readValue(json, new TypeReference<HashMap<Path, String>>() {});
System.out.println(path2String);
}
}
Above code prints:
{
"home/des" : "Desktop",
"user/downloads" : "Downloads"
}
{home/des=Desktop, user/downloads=Downloads}
Upvotes: 3