4

We are working with a REST service that provides json that will consist of some standard properties, as well as a number of dynamic properties.

For example:

{
  id: 123,
  name: "some name",
  custom_name: "Some value",
  some_other_custom_name: "Some other value",
}

Ideally, I'd like to have the class designed as follows:

public class MyObject{
  @JsonProperty int id;
  @JsonProperty String name;
  private Map<String, String> customVals;

  public int getId(){
    return id;
  }

  public String getName(){
    return name;
  }

  public String getCustomVal(String key){
    return customVals.get(key);
  }
}

Is there any way to convince Jackson to push the custom values into the Map (or achieve equivalent functionality)?

Right now, I'm just deserializing the whole object into a Map, and wrapping that in my business object, but it's not nearly as elegant as it would be if the deserialization would take care of it.

Alexey Gavrilov
  • 10,593
  • 2
  • 38
  • 48
Kevin Day
  • 16,067
  • 8
  • 44
  • 68

2 Answers2

7

You can use Jackson @JsonAnySetter and @JsonAnyGetter annotations.

Here is a complete example:

public class JacksonAnyGetter {

    static final String JSON = "{"
            + "  \"id\": 123,"
            + "  \"name\": \"some name\","
            + "  \"custom_name\": \"Some value\","
            + "  \"some_other_custom_name\": \"Some other value\""
            + "}";

    static class Bean {
        public int id;
        public String name;
        private Map<String, Object> properties = new HashMap<>();

        @JsonAnySetter
        public void add(String key, String value) {
            properties.put(key, value);
        }

        @JsonAnyGetter
        public Map<String, Object> getProperties() {
            return properties;
        }

        @Override
        public String toString() {
            return "Bean{" +
                    "id=" + id +
                    ", name='" + name + '\'' +
                    ", properties=" + properties +
                    '}';
        }
    }

    public static void main(String[] args) throws IOException {
        final ObjectMapper mapper = new ObjectMapper();
        final Bean bean = mapper.readValue(JSON, Bean.class);
        System.out.println(bean);
        final String json = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(bean);
        System.out.println(json);
    }
}

Output:

Bean{id=123, name='some name', properties={custom_name=Some value, some_other_custom_name=Some other value}}

{
  "id" : 123,
  "name" : "some name",
  "custom_name" : "Some value",
  "some_other_custom_name" : "Some other value"
}
Alexey Gavrilov
  • 10,593
  • 2
  • 38
  • 48
  • Shouldn't you add `@JsonIgnoreProperties({"properties"})` so properties won't be added twice? – reynev May 16 '17 at 11:25
  • With field type serialization `properties` serialized twice (as field also). With `@JsonIgnoreProperties({"properties"})` serialized nowhere.. – Grigory Kislin Nov 30 '22 at 11:50
1

Problem with dynamic properties (e.g. in Spring 3.0 ProblemDetail) and Jackson field type serialization: props duplicate in answer

{
  "type": "about:blank",
  "title": null,
  "status": 400,
  "detail": "Invalid request content.",
  "instance": "/api/register",
  "properties": {
    "email": "должно иметь формат адреса электронной почты"
  },
  "email": "должно иметь формат адреса электронной почты"
}

@JsonIgnoreProperties({"properties"}) remove email everywhere.

Found fix:

    @JsonAutoDetect(fieldVisibility = NONE, getterVisibility = ANY)
    interface MixIn {
        @JsonAnyGetter
        Map<String, Object> getProperties();
    }

    @Autowired
    void configureAndStoreObjectMapper(ObjectMapper objectMapper) {
        // https://stackoverflow.com/questions/7421474/548473
        objectMapper.addMixIn(ProblemDetail.class, MixIn.class);
    }
Grigory Kislin
  • 16,647
  • 10
  • 125
  • 197