Mapping object values accounting for multiple Strings

Viewed 84

I am receiving the following json from an external rest call.

{
    // other keys coming in too, but already mapped
    "someData": {
        "someContent": {
        "item1": "",
        "otherItem25": "",
        "anotherData34": ""
        }
    }
}

I wish to map it to an object.

I could do the following which works.

Solution 1

private Map<String, Map<String, String>> someData;

But looking to map it to named objects.

Like following which works too.

Solution 2

class Root {
    private someData someData;
}

class SomeData {
    private SomeContent someContent;
}

class SomeContent {
    @JsonProperty("item1")
    private String one;

    @JsonProperty("otherItem25")
    private String two;

    @JsonProperty("anotherData34")
    private String three;
}

The issue is with the last bit where I am individually naming the Strings. I don't want to do that cos this is meant to grow and I could land with about 50 Strings soon. I don't want to create 50 Strings.

Other than the above 2 solutions, is there a better way to map this json to an Object?

Something like this would have been ideal but won't work cos there is nothing to identify as nnerData in the json.

class SomeContent {
    private Map<String, String> innerData;
}

Please advice.

P.S: I can't modify the json

3 Answers

Looks like the "ideal case" can be implemented with @JsonCreator annotation and passing the map with "innerData" into constructor.

class TestJson {

    @Test
    void test() throws Exception {

        var json = "{" +
                   "    \"someData\": {" +
                   "        \"someContent\": {" +
                   "            \"item1\": \"hello\"," +
                   "            \"otherItem25\": \"245hb24bt\"," +
                   "            \"anotherData34\": \"b42tb245\"" +
                   "}}}";

        var value = new ObjectMapper().readValue(json, Root.class);

        assertThat(value.someData.someContent.innerData.get("item1"))
            .isEqualTo("hello");
    }


    // here we use "public" just to make the code shorter
    // and let Jackson bind properties to the public fields
   
    static class Root {

       public SomeData someData;
    }

    static class SomeData {

       public SomeContent someContent;
    }

    static class SomeContent {

       public final Map<String, Object> innerData;

       @JsonCreator
       public SomeContent(Map<String, Object> anyName) {
           this.innerData = Map.copyOf(anyName);
           // notice that the map "anyName" is mutable
       }
    }
}

Please, double check if there is no over-engineering here and that is exactly what is required in the solution. Probably, it would be enough to have simply Map type for "someContent" field (and also consider to handle map mutability in this case).

class SomeData {
    Map<String, String> someContent;
}

If I'm reading the question correctly, it looks like you want to map most of the object as Java beans, but want the someContent mapped as a Map. Assuming that's the case, you can map the various non-bean properties as normal, and then map someContent as a Map<String, String> on the inner SomeData type.

class Root {
    private SomeData someData;
}

class SomeData {
    private Map<String, String> someContent;
}

map will be best option in my opinion if your json will be dynamic and will change very often. if you only care about certain properties from any random thing that might be added to response , then just map properties you need to a POJO and use @JsonIgnoreProperty(ignore=unknown) annotation to ignore additional things that will come in response that you arent even interested.

Related