4

I am using Jersey 2.17 with Moxy and I have functions like :

@Produces(APPLICATION_JSON)
@Restricted
public List<User> getFriends(
        @PathParam("user") String user
) {
    return userDAO.getFriends(user);
}

User.preferences is a HashMap.

It works fine for almost all Objects except for a HashMap which gets translated into:

"preferences":{"entry":[{"key":{"type":"string","value":"language"},"value":{"type":"string","value":"en"}},{"key":{"type":"string","value":"country"},"value":{"type":"string","value":"US"}}]}

but what I would really like to return is just a javascript object like:

preferences:{"language":"en","country":"US"}

How can I do that?

Paul Samsotha
  • 205,037
  • 37
  • 486
  • 720
arisalexis
  • 2,079
  • 2
  • 21
  • 42

1 Answers1

9

Yeah MOXy and Maps don't work well. It's sad, as JSON is nothing more than mapped key/value pairs. If you want to use MOXy, you will need to use an XmlAdapter. In which case, the way you want the JSON, you will need to create a type (class) that has the name of all possible preferences. Arbitrary key value pairs should be in the form you require, but since MOXy can't do maps, you'll need to map it to your own type. For instance

public class PreferencesAdapter extends XmlAdapter<Preference, HashMap<String, String>> {

    @XmlRootElement
    public static class Preference {
        public String language;
        public String country;
    }

    @Override
    public HashMap<String, String> unmarshal(Preference p) throws Exception {
        HashMap<String, String> map = new HashMap<>();
        map.put("language", p.language);
        map.put("country", p.country);
        return map;
    }


    @Override
    public Preference marshal(HashMap<String, String> v) throws Exception {
        Preference p = new Preference();
        p.language = v.get("language");
        p.country = v.get("country");
        return p;
    }
}

Your DTO

@XmlRootElement
public class User {
    @XmlJavaTypeAdapter(PreferencesAdapter.class)
    public HashMap<String, String> preferences;
}

But if we're doing to do all this, why don't we just use a Preferences object in the first place instead of a Map? That was a rhetorical question. I totally understand why you wouldn't. But this is one of the limitations of MOXy, as it makes heavy us of JAXB under the hood, and JAXB has never played nicely with Map, which is sad, as like I said, JSON is really nothing more than a Map of key values.

For this reason, and actually many others I have encountered in the past, I do no recommend using MOXy even though it is recommended by Jersey. Instead, use Jackson. Jackson has been the defacto Java goto for JSON processing for a while. For Jackson, just use this dependency

<dependency>
    <groupId>org.glassfish.jersey.media</groupId>
    <artifactId>jersey-media-json-jackson</artifactId>
    <version>${jersey.version}</version>
</dependency>

If you take out the MOXy dependency, this Jackson module should work out the box. Otherwise if you leave the MOXy dependency, you will need to register the JacksonFeature

Paul Samsotha
  • 205,037
  • 37
  • 486
  • 720
  • the reason why I want to use maps is that I persist the objects in the database without an ORM so I need to convert JSONObjects into Strings and unmarshal them afterwards while maps work oob. – arisalexis Mar 29 '15 at 08:51
  • it is working correctly and out of the box. writing here may help another user: jackson serializes null values too and also it needs explicit annotation with @Produces(APPLICATION_JSON) in the resources. – arisalexis Mar 31 '15 at 08:57
  • 1
    If you want to exclude null fields with Jackson, you can annotate your classes with `@JsonInclude(JsonInclude.Include.NON_NULL)`, or without having to annotate all classes, you can [configure the `ObjectMapper`](http://stackoverflow.com/q/11757487/2587435) in the [`ContextResolver`](http://stackoverflow.com/a/29202555/2587435) – Paul Samsotha Mar 31 '15 at 09:09
  • 1
    +1 for going through the entire example and reaching the logical conclusion that Jackson fits better in this case. I've been fighting with MOXy and Maps these last few days and you saved me a lot of trouble. – agmangas Dec 03 '15 at 10:54
  • any tip on how to make this work, if the keys are not known beforehand? like the key can be arbitrary string – logi0517 Jan 06 '18 at 11:18
  • @ZoltánUmlauf https://gist.github.com/psamsotha/624d0dc1cc8a00c417e57f8fce8d905d. I just tried it though with Jackson on the Map. Doesn't look like it works. Thats why I said to use Jackson's version of the converter, which is the JsonSerializer, JsonDeserialzier – Paul Samsotha Jan 06 '18 at 17:48