0

Here is my class

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.*;

public class Util {

    public static void main(String[] args) throws JsonProcessingException {
        String json = "{ \"argA\" : 5, \"unneededkey\" : 6}";
        ObjectMapper mapper = new ObjectMapper();
        MyObject object3 = mapper.readValue(json, MyObject.class);
        System.out.println(object3);

    }
    @ToString
    @RequiredArgsConstructor
    @JsonIgnoreProperties(ignoreUnknown = true)
    public static class MyObject {
        public MyObject(int argA) {
            this.argA = argA;
        }

        public MyObject(int argA, boolean argB) {
            this.argA = argA;
            this.argB = argB;
        }

        public MyObject(int argA, int argC, int argD) {
            this.argA = argA;
            this.argC = argC;
            this.argD = argD;
        }

        public MyObject(int argA, String argE) {
            this.argA = argA;
            this.argE = argE;
        }

        public int argA = 1;
        public boolean argB;
        public int argC = 4;
        public int argD = 5;
        public String argE;
    }
}

@JsonIgnoreProperties(ignoreUnknown = true) works but this ignores all unneeded keys. What if I only want to ignore certain unneeded keys and if I am given a key thats not part of my object constructor or in the unneeded set, it will throw an exception.

1 Answers1

0

My suggested approach:

Keep the @JsonIgnoreProperties(ignoreUnknown = true) annotation on your class.

Assume the following input JSON:

{
    "argA": 5,
    "unneededkey": 6,
    "unexpectedKey": 99
}

Create a set containing the allowed field names (where "allowed" is a field which exists in your MyObject class, or it can be safely ignored):

Set<String> allowedFields = new HashSet(Arrays.asList(
            "argA", "argB", "argC", "argD", "argE",
            "unneededkey", "unneededkey2"));

Then process the JSON as follows:

ObjectMapper mapper = new ObjectMapper();

JsonNode rootNode = mapper.readTree(json);
Iterator<String> fieldNames = rootNode.fieldNames();
boolean fieldFailure = false;
Set<String> unexpectedFields = new HashSet();

while (fieldNames.hasNext()) {
    String fieldName = fieldNames.next();
    if (!allowedFields.contains(fieldName)) {
        fieldFailure = true;
        unexpectedFields.add(fieldName);
    }
}

You can then choose how to proceed, based on the boolean fieldFailure. Either throw an exception (using the list of unexpected field names for information), or create your object in the usual way:

MyObject object = mapper.readValue(json, MyObject.class);

The down side to this is that it may traverse the JSON twice - once to check for problems, and again (assuming no problems) to deserialize the data.

Points to note:

(1) The above code assumes a flat JSON structure - no embedded objects - which matches what you have in your question. For more flexibility, you can use the following findKeys method to recursively visit all nodes:

Map<String, Object> treeMap = mapper.readValue(json, Map.class);
List<String> keys  = Lists.newArrayList();
List<String> result = findKeys(treeMap, keys);

private List<String> findKeys(Map<String, Object> treeMap, List<String> keys) {
    treeMap.forEach((key, value) -> {
        if (value instanceof LinkedHashMap) {
            Map<String, Object> map = (LinkedHashMap) value;
            findKeys(map, keys);
        }
        keys.add(key);
    });

    return keys;
}

Acknowledgement: See this answer for details.

(2) I considered a custom deserializer:

public class MyDeserializer extends StdDeserializer<Util.MyObject> { 
    ...
}

But you would still have the same issue - not to mention the extra work needed to invoke the correct constructor, depending on the specific combination of non-null field values it finds.

andrewJames
  • 19,570
  • 8
  • 19
  • 51