Jackson not recognizing fields that exist

So here's my JSON

{"totalSize":46,"done":true,"records":[{"Name":"Wamu I","Start_Date__c":"2016-09-26T16:56:10.000+0000","Status__c":"Completed","Type__c":"Your were expecting success, but In reality it was I, Dio!!!"}]}

And here are my two entity classes:

@JsonIgnoreProperties(ignoreUnknown = true)
public class EsidesiJobEntity {

    @JsonProperty("totalSize")
    private @Getter @Setter Integer totalSize;

    @JsonProperty("done")
    private @Getter @Setter Boolean isDone;

    @JsonProperty("records")
    private @Getter @Setter List<KarsEntity> records;

    @Override
    @JsonIgnore
    public String toString(){

        List<String> recordsObjectString = new ArrayList<String>();

        this.records.forEach((record) -> 
        {
            recordsObjectString.add(record.toString());
        });
        return "{ totalSize:"+this.totalSize+", isDone:"+this.isDone+", records:["+recordsObjectString.toString()+"]";
    }

}

@JsonIgnoreProperties(ignoreUnknown = true)
public class KarsEntity {

    @JsonProperty("Name")
    private @Getter @Setter String name;

    @JsonProperty("Start_Date__c")
    private @Getter @Setter String startDate;

    @JsonProperty("Status__c")
    private @Getter @Setter String status;

    @Override
    public String toString(){
        return "{ name:"+this.name+", startDate:"+this.startDate+", status:"+this.status+"}";
    }
}

for some reason, when I map that json string to the EsidesiJobEntity, I get the following error:

Unrecognized field "totalSize"

BUT IT DEFINITELY EXISTS IN BOTH IN BOTH THE JSON AND THE ENTITY!

Here's the code I wrote to map the string to the entity for reference:

EsidesiEntity apexJobResponseEntity;

ObjectMapper apexMapper = new ObjectMapper();
try {
    apexJobResponseEntity = apexMapper.readValue(apexResponseString, EsidesiEntity.class);
} ...

Am I missing something really basic?

(BTW, If there's some inconsistency in the Class/Entity names, its because I renamed them before posting them online. Let me know and I'll fix them as I see them. )

Thanks!

You are using Lombok. Jackson can't see your getter and setter methods.

So you have two options:

  1. Do not use Lombok and implement the getter and setter methods
  2. Use Lombok with this additional library: jackson-lombok

If you are using maven, so add jackson-lombok to your pom.xml:

<dependency>
    <groupId>com.xebia</groupId>
    <artifactId>jackson-lombok</artifactId>
    <version>1.1</version>
</dependency>

Configure then your ObjectMapper in this way:

ObjectMapper apexMapper = new ObjectMapper();
apexMapper.setAnnotationIntrospector(new JacksonLombokAnnotationIntrospector());
[...]

you would normally annotate your data class to have the filter applied:

@jsonfilter("test")
class data {

you have specified that you can't use annotations on the class. you could use mix-ins to avoid annotating data class.

@jsonfilter("test")
class datamixin {}

mixins have to be specified on an objectmapper and you specify you don't want to reconfigure that. in such a case, you can always copy the objectmapper with its configuration and then modify the configuration of the copy. that will not affect the original objectmapper used elsewhere in your code. e.g.

objectmapper mymapper = mapper.copy();
mymapper.addmixin(data.class, datamixin.class);

and then write with the new objectmapper

string json = mymapper.writer(filterprovider).writevalueasstring(new data());
system.out.println(json); // output: {"data1":"value1"}

despite being hard to manage as others have pointed out, you can do what you want. add a custom deserializer to handle this situation. i rewrote your beans because i felt your attribute class was a bit misleading. the attributeentry class in the object that is an entry in that "attributes" list. the valueobject is the class that represents that "key"/topic/"label" object. those beans are below, but here's the custom deserializer. the idea is to check the type in the json, and instantiate the appropriate attributeentry based on its "value" type.

public class attributedeserializer extends jsondeserializer<attributeentry> {
    @override
    public attributeentry deserialize(jsonparser p, deserializationcontext ctxt) throws ioexception, jsonprocessingexception {
        jsonnode root = p.readvalueastree();
        string name = root.get("name").astext();
        if (root.get("value").isobject()) {
            // use your object mapper here, this is just an example
            valueobject attribute = new objectmapper().readvalue(root.get("value").astext(), valueobject.class);
            return new attributeentry(name, attribute);
        } else if (root.get("value").istextual()) {
            string stringvalue = root.get("value").astext();
            return new attributeentry(name, stringvalue);
        } else {
            return null; // or whatever
        }
    }
}

because of this ambiguous type inconvenience, you will have to do some type checking throughout your code base.

you can then add this custom deserializer to your object mapper like so:

objectmapper objectmapper = new objectmapper();
simplemodule simplemodule = new simplemodule();
simplemodule.adddeserializer(attributeentry.class, new attributedeserializer());
objectmapper.registermodule(simplemodule);

here's the attributeentry:

public class attributeentry {
    private string name;
    private object value;

    public attributeentry(string name, string value) {
        this.name = name;
        this.value = value;
    }

    public attributeentry(string name, valueobject attributes) {
        this.name = name;
        this.value = attributes;
    }
    /* getters/setters */
}

here's the valueobject:

public class valueobject {
    private string key;
    private string label;
    /* getters/setters */
}

try something like that. if you use jsonnode your life gonna be easier.

jsonnode node = mapper.readvalue("[{"listed_count":1720,"status":{"retweet_count":78}}]]", jsonnode.class);

system.out.println(node.findvalues("retweet_count").get(0).asint());

one of the options is to use @jsoncreator annotation:

    @jsoncreator
    public customer(
            @jsonproperty("name") string name,
            @jsonproperty("age")  int age,
            @jsonproperty("street") string street,
            @jsonproperty("postalcode")   string postalcode
    ) {
        this.name = name;
        this.age = age;
        this.address = new address();
        this.address.street = street;
        this.address.postalcode = postalcode;
    }

second option is create custom deserializer and bind your class with deserializer using @jsondeserialize annotation

@jsondeserialize(using = customerdeserializer.class)
public static class customer{
  ....
}

public class customerdeserializer extends stddeserializer<customer> {

    public customerdeserializer() {
        super(customer.class);
    }

    @override
    public customer deserialize(jsonparser p, deserializationcontext ctxt) throws ioexception, jsonprocessingexception {
        customer customer = new customer();
        jsonnode treenode = p.readvalueastree();
        if (treenode == null) {
            return null;
        }
        customer.setname(treenode.get("name").astext());
        customer.setage(treenode.get("age").asint());
        address address = new address();
        address.setstreet(treenode.get("street").astext());
        address.setpostalcode(treenode.get("postalcode").astext());
        customer.setaddress(address);
        return customer;
    }
}

as third option, you can use @jsonanysetter with some kind of post construct processing:

public interface postconstruct {
    void postconstruct();
}

public class customer implements postconstruct {
    //mapping 

    private map<string, object> additionalfields = new hashmap<>();

    @jsonanysetter
    public void setadditionalvalue(string key, object value) {
        additionalfields.put(key, value);
    }

    @override
    public void postconstruct() {
        address = new address();
        address.setstreet(string.valueof(additionalfields.get("street")));
        address.setpostalcode(string.valueof(additionalfields.get("postalcode")));
    }
}


public static class postconstructdeserializer extends delegatingdeserializer {
    private final jsondeserializer<?> deserializer;

    public postconstructdeserializer(jsondeserializer<?> deserializer) {
        super(deserializer);
        this.deserializer = deserializer;
    }

    @override
    protected jsondeserializer<?> newdelegatinginstance(jsondeserializer<?> newdelegatee) {
        return deserializer;
    }

    @override
    public object deserialize(jsonparser jp, deserializationcontext ctxt) throws ioexception {
        object result = _delegatee.deserialize(jp, ctxt);
        if (result instanceof postconstruct) {
            ((postconstruct) result).postconstruct();
        }
        return result;
    }
}


//using of post construct deserializer

    objectmapper mapper = new objectmapper();
    simplemodule module = new simplemodule();
    module.setdeserializermodifier(new beandeserializermodifier() {
        @override
        public jsondeserializer<?> modifydeserializer(deserializationconfig config,
                                                      beandescription beandesc,
                                                      final jsondeserializer<?> deserializer) {
            return new postconstructdeserializer(deserializer);
        }
    });
    mapper.registermodule(module);

Tags: Java Json Jackson