1

I got an issue when trying to deserialise xml using jackson.

I am using the following function to deserialise my xml from the file.

The Xml file is as-:

https://github.com/Eno-Gerguri/Pygame-Studio/blob/master/Settings/defaultSettings.xml

Here is the function I'm using to deserialise the object:

    public Settings deserializeSettings(File settingsFile) {
        XmlMapper xmlMapper = new XmlMapper();
        String xml = null;
        Settings settings = null;
        
        try {
            xml = inputStreamToString(new FileInputStream(settingsFile));
            
        } catch (FileNotFoundException e) {
            e.printStackTrace();
            
        } catch (IOException e) {
            e.printStackTrace();
            
        }
        try {
            settings = xmlMapper.readValue(xml, Settings.class);
            
        } catch (JsonMappingException e) {
            e.printStackTrace();
            
        } catch (JsonProcessingException e) {
            e.printStackTrace();
            
        }
        
        return settings;
    }
    
    private String inputStreamToString(InputStream inputStream) throws IOException {
        StringBuilder stringBuilder = new StringBuilder();
        String line;
        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
        while ((line = bufferedReader.readLine()) != null) {
            stringBuilder.append(line);
        }
        bufferedReader.close();
        return stringBuilder.toString();
    }

The Settings object I'm using that I am deserialising into:-

https://github.com/Eno-Gerguri/Pygame-Studio/blob/master/src/com/pygame_studio/settings/Settings.java

The sub-class that the Settings object uses:

https://github.com/Eno-Gerguri/Pygame-Studio/blob/master/src/com/pygame_studio/settings/appearance_and_behavior/AppearanceAndBehavior.java

The sub-sub-class that the Settings object uses:

https://github.com/Eno-Gerguri/Pygame-Studio/blob/master/src/com/pygame_studio/settings/appearance_and_behavior/Font.java

When I try to deserialise the object in a separate file:

private Settings defaultSettings = settingsManager.deserializeSettings(DefaultSettings.DEFAULT_SETTINGS_FILE_DIRECTORY);

The following error log

com.fasterxml.jackson.databind.JsonMappingException: N/A
 at [Source: (StringReader); line: 1, column: 356] (through reference chain: com.pygame_studio.settings.Settings["appearanceAndBehavior"]->com.pygame_studio.settings.appearance_and_behavior.AppearanceAndBehavior["font"]->com.pygame_studio.settings.appearance_and_behavior.Font["fontDirectory"])
    at com.fasterxml.jackson.databind.JsonMappingException.from(JsonMappingException.java:278)
    at com.fasterxml.jackson.databind.deser.SettableBeanProperty._throwAsIOE(SettableBeanProperty.java:611)
    at com.fasterxml.jackson.databind.deser.SettableBeanProperty._throwAsIOE(SettableBeanProperty.java:599)
    at com.fasterxml.jackson.databind.deser.impl.MethodProperty.deserializeAndSet(MethodProperty.java:143)
    at com.fasterxml.jackson.databind.deser.BeanDeserializer.vanillaDeserialize(BeanDeserializer.java:288)
    at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:151)
    at com.fasterxml.jackson.databind.deser.impl.MethodProperty.deserializeAndSet(MethodProperty.java:129)
    at com.fasterxml.jackson.databind.deser.BeanDeserializer.vanillaDeserialize(BeanDeserializer.java:288)
    at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:151)
    at com.fasterxml.jackson.databind.deser.impl.MethodProperty.deserializeAndSet(MethodProperty.java:129)
    at com.fasterxml.jackson.databind.deser.BeanDeserializer.vanillaDeserialize(BeanDeserializer.java:288)
    at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:151)
    at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4202)
    at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3205)
    at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3173)
    at com.pygame_studio.settings.SettingsManager.deserializeSettings(SettingsManager.java:75)
    at com.pygame_studio.start_menu.StartMenu.<init>(StartMenu.java:24)
    at com.pygame_studio.PygameStudio.<init>(PygameStudio.java:18)
    at com.pygame_studio.PygameStudio.main(PygameStudio.java:25)
Caused by: java.lang.NullPointerException
    at com.pygame_studio.settings.appearance_and_behavior.Font.setFontDirectory(Font.java:115)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:564)
    at com.fasterxml.jackson.databind.deser.impl.MethodProperty.deserializeAndSet(MethodProperty.java:141)
    ... 15 more

I know that in the function: com.pygame_studio.settings.appearance_and_behavior.Font.setFontDirectory

public void setFontDirectory(String fontDirectory) {
        if (externalFonts.containsKey(fontDirectory)) {
            this.fontDirectory = externalFonts.get(fontDirectory);
        } else if (localFonts.contains(fontDirectory)) {
            this.fontDirectory = fontDirectory;
        } else {
            this.fontDirectory = this.getFallbackFont();
        }
    }

externalFonts Hashtable is null because it has not been initialised however, it should be as in the constructer:

    public Font() {
        super();
    }
    
    public Font(File externalFontDirectory, String fontName, int fontStyle, int fontSize, String fallbackFont) {
        this.setExternalFontDirectory(externalFontDirectory);
        
        this.setExternalFonts();  // External fonts is set before the font directory.
        this.setLocalFonts();
        
        this.setFontDirectory(fontName);  // Font directory being set after externalFonts.
        
        this.setFontStyle(fontStyle);
        
        this.setFontSize(fontSize);
        
        this.setFallbackFont(fallbackFont);
    }

setExternalFonts method:

    public void setExternalFonts() {
        externalFonts = getExternalFonts(this.externalFontDirectory);
    }

getExternalFonts method:

    public static Hashtable<String, String> getExternalFonts(File externalFontDirectory) {
        Hashtable<String, String> externalFonts = new Hashtable<>();
        
        final File[] directoryFiles = externalFontDirectory.listFiles();
        
        if (directoryFiles != null) {
            for (File file : directoryFiles) {
                if (file.isDirectory()) {  // If the file is a sub-directory.
                    externalFonts.putAll(getExternalFonts(file));  // Calls itself onto the directory.
                } else if (file.getName().contains(".ttf")) {  // If the file is a font.
                    String fontName = file.getName().replace(".ttf", "");  // Gets the name of the font.
                    externalFonts.put(fontName, file.getPath());  // Puts the, name of the font : font's directory, into the Hashtable.
                }
            }
        }
        return externalFonts;
    }

However, my IDE Eclipse tells me that this code is never reached, when it should be to initialise externalFonts.

Why is this method not being called? How do I fix this issue so I can successfully deserialise the xml into a "Settings" object?

Thanks in advance!

N.Neupane
  • 281
  • 1
  • 4
  • 17
Eno Gerguri
  • 639
  • 5
  • 22

1 Answers1

1

The specific problem is here in the Font class:

public static Hashtable<String, String> externalFonts;

First I would recommend changing this from Hashtable to HashMap - these days there is no reason (in my experience) to use Hashtable over any of the more modern collection classes. You can read community opinions here.

Second, the field needs to be initialized, so it is not null - like you do for the localFonts list.

So that gives us this:

public static Map<String, String> externalFonts = new HashMap();

You will then need to replace the remaining references to Hashtable, of course.

When I made that change, it fixed the specific problem you reported.

andrewJames
  • 19,570
  • 8
  • 19
  • 51
  • Would I replace all references to `Hashtable` with `Map` or `HashMap`? I assume `Map`. – Eno Gerguri Jun 22 '20 at 22:00
  • 1
    Typically, the interface (`Map`) is what you pass around: `public Map getExternalFonts()`. And use the implementation (`HashMap`) in assignments: `Map externalFonts = new HashMap<>();`. Does that make sense? – andrewJames Jun 22 '20 at 22:17
  • 1
    You can read more about the difference and usage [here](https://stackoverflow.com/questions/1348199/what-is-the-difference-between-the-hashmap-and-map-objects-in-java). And apologies if I'm telling you things you already know. – andrewJames Jun 22 '20 at 22:18