3

When serializing a Person class using Jackson where Person is:

public class Person {

    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }

    @Override
    public String toString() {
        return "My name is "+name+" ("+age+")";
    }

}

I get the following output:

Map<Person, String> map = new HashMap<>();
Person john = new Person("John", 20);
map.put(john, "abc");

ObjectMapper objectMapper = new ObjectMapper();
objectMapper.writeValue(System.out, map); //1
objectMapper.writeValue(System.out, john); //2

1:

{"My name is John (20)":"abc"}

2:

{"name":"John","age":20}

Is there an easy way (without a custom serializer) to change the map key to use the name attribute instead of the toString output in #1, without losing the serialization output of the Person object in #2, and without changing the toString return value? I would expect there is an annotation for getName that does this, but can't seem to find it.

tb189
  • 1,942
  • 3
  • 22
  • 37
  • @shmosel: my question was if this is possible without a custom serializer. The duplicate question is answered with a custom serializer, so I guess the answer is: no it's not possible? – tb189 Jun 28 '18 at 22:42
  • Seems it's not. – shmosel Jun 28 '18 at 22:43
  • An interesting set of constraints on the question... Without custom serializers, and without changing the serialization output of the Person object. Assuming you are looking for the result to be `{"John":"abc"}` I have a few solutions, but it seems this no longer accepts answers. – D. May Jun 29 '18 at 03:05
  • It is too restrictive to type the anser in the comments. Are Custom Collection or Java8 streams allowed in your case? – D. May Jun 29 '18 at 03:12
  • @D.May Reopened. Have at it. – shmosel Jun 29 '18 at 03:42

1 Answers1

1

The custom serializer and addKeySerializer seems to be a better approach, although it is a bit cumbersome to create a module, add a Serializer or KeySerializer and then register the module with your mappers.

If for some reason (as you stated) you are not able to use a custom serializer. Below are a couple options that may help you

Both produce the output

{"John":"abc"}

When writing the map, but keep the single object unchanged

{"name":"John","age":20}

Java 8 Streams to Map on output

Depending on how intricate your real objects are, compared to the simple example you might be able to simply do a mapping during or before your writeValue call.

    mapper.writeValue(System.out,
                      map.entrySet().stream().collect(
                         Collectors.toConcurrentMap(
                            k -> k.getKey().getName(),
                            Function.identity())));

Implement JsonSerializable on Collection

If you are able to change your collection to a custom version that extends HashMap.

class PersonHashMap extends HashMap<Person, String> implements JsonSerializable {

    @Override
    public void serialize(JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
        jsonGenerator.writeStartObject();
        for (Entry<Person, String> e : entrySet()) {
            jsonGenerator.writeStringField(e.getKey().getName(), e.getValue());
        }
        jsonGenerator.writeEndObject();
    }

    @Override
    public void serializeWithType(JsonGenerator jsonGenerator, SerializerProvider serializerProvider, TypeSerializer typeSerializer) throws IOException {
        .
        .
        .
    }
}

The drawback is you would need to be in control of the initialization of the map, to do something like this:

Map<Person, String> map = new PersonHashMap();

But then again, that is all that is needed, no additional module, or registration with your object mapper as you would have to do with a JsonSerializer.

D. May
  • 302
  • 1
  • 7
  • 1
    We could definitely use a `@JsonMapKey` annotation that can be applied to a getter to redefine (from the `toString` default) the what the map key looks like – tb189 Jun 30 '18 at 12:40