jaxb-ri icon indicating copy to clipboard operation
jaxb-ri copied to clipboard

JAXB ignores overridden getter giving XXX is an interface, and JAXB can't handle interfaces

Open PavelTurk opened this issue 6 months ago • 0 comments

This is my code:

import jakarta.xml.bind.JAXBContext;
import jakarta.xml.bind.JAXBException;
import jakarta.xml.bind.Marshaller;
import jakarta.xml.bind.Unmarshaller;
import jakarta.xml.bind.annotation.XmlAttribute;
import jakarta.xml.bind.annotation.XmlElement;
import jakarta.xml.bind.annotation.XmlRootElement;
import jakarta.xml.bind.annotation.adapters.XmlAdapter;
import jakarta.xml.bind.annotation.adapters.XmlJavaTypeAdapter;

import java.io.StringReader;
import java.io.StringWriter;
import java.util.HashSet;
import java.util.Set;


public class Test {

    public static interface Teacher {
        String getName();
    }

    public static class ConcreteTeacher implements Teacher {
        private String name;

        public ConcreteTeacher() {

        }

        public ConcreteTeacher(String name) {
            this.name = name;
        }

        @Override
        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        @Override
        public String toString() {
            return name;
        }
    }

    public static class GroupParent {

        private Set<Teacher> teachers;

        public Set<Teacher> getTeachers() {
            return teachers;
        }

        public void setTeachers(Set<Teacher> teachers) {
            this.teachers = teachers;
        }
    }

    @XmlRootElement(name = "group")
    public static class Group extends GroupParent {

        @Override
        @XmlElement(name = "teachers")
        @XmlJavaTypeAdapter(TeacherAdapter.class)
        public Set<Teacher> getTeachers() {
            return super.getTeachers();
        }
    }

    public static class TeacherAdapter extends XmlAdapter<TeacherAdapter.AdaptedTeachers, Set<Teacher>> {

        public static class AdaptedTeachers {
            @XmlElement(name = "teacher")
            public Set<AdaptedTeacher> teacherList = new HashSet<>();
        }

        public static class AdaptedTeacher {
            @XmlAttribute(name = "name")
            public String name;
        }

        @Override
        public Set<Teacher> unmarshal(AdaptedTeachers adaptedTeachers) throws Exception {
            Set<Teacher> teachers = new HashSet<>();
            for (AdaptedTeacher adaptedTeacher : adaptedTeachers.teacherList) {
                teachers.add(new ConcreteTeacher(adaptedTeacher.name));
            }
            return teachers;
        }

        @Override
        public AdaptedTeachers marshal(Set<Teacher> teachers) throws Exception {
            AdaptedTeachers adaptedTeachers = new AdaptedTeachers();
            for (Teacher teacher : teachers) {
                AdaptedTeacher adaptedTeacher = new AdaptedTeacher();
                adaptedTeacher.name = teacher.getName();
                adaptedTeachers.teacherList.add(adaptedTeacher);
            }
            return adaptedTeachers;
        }
    }

    private static final String XML_STRING = """
        <group>
            <teachers>
                <teacher name="Alice"/>
                <teacher name="Bob"/>
            </teachers>
        </group>
    """;

    public static void main(String[] args) {
        try {
            JAXBContext context = JAXBContext.newInstance(Group.class);

            Group group = new Group();
            Set<Teacher> teachers = new HashSet<>();
            teachers.add(new ConcreteTeacher("Alice"));
            teachers.add(new ConcreteTeacher("Bob"));
            group.setTeachers(teachers);

            Marshaller marshaller = context.createMarshaller();
            marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
            StringWriter writer = new StringWriter();
            marshaller.marshal(group, writer);
            String xml = writer.toString();
            System.out.println("Serialized XML:");
            System.out.println(xml);

            Unmarshaller unmarshaller = context.createUnmarshaller();
            StringReader reader = new StringReader(XML_STRING);
            Group deserializedGroup = (Group) unmarshaller.unmarshal(reader);
            System.out.println("Deserialized Group:");
            for (Teacher teacher : deserializedGroup.getTeachers()) {
                System.out.println(teacher.getName());
            }
        } catch (JAXBException e) {
            e.printStackTrace();
        }
    }
}

If you run it, it will give:

org.glassfish.jaxb.runtime.v2.runtime.IllegalAnnotationsException: 1 counts of IllegalAnnotationExceptions
com.foo.mavenproject19.Test$Teacher is an interface, and JAXB can't handle interfaces.
	this problem is related to the following location:
		at com.foo.mavenproject19.Test$Teacher
		at public java.util.Set com.foo.mavenproject19.Test$GroupParent.getTeachers()
		at com.foo.mavenproject19.Test$GroupParent
		at com.foo.mavenproject19.Test$Group

But, if you update it to

    @XmlRootElement(name = "group")
    public static class Group { 

        private Set<Teacher> teachers;

        @XmlElement(name = "teachers")
        @XmlJavaTypeAdapter(TeacherAdapter.class)
        public Set<Teacher> getTeachers() {
            return teachers;
        }

        public void setTeachers(Set<Teacher> teachers) {
            this.teachers = teachers;
        }
    }

Then everything works fine.

The problem of this issue is important in case when you have a class from one project that doesn't use XML and you need to write/read it to/from XML file in another project, so you use inheritance.

PavelTurk avatar Aug 15 '24 12:08 PavelTurk