Jackson Serialization for subclasses - java

In the below example, I have a primary class - A and its subclass - B. Both can be used as a property in the general class X.
public class A
{
#JsonProperty("primary_key")
public final String primaryKey;
#JsonCreator
A(#JsonProperty("primary_key") String primaryKey)
{
this.primaryKey = primaryKey;
}
}
public class B extends A
{
#JsonProperty("secondary_key")
public final String secondaryKey;
#JsonCreator
B(#JsonProperty("primary_key") String primaryKey, #JsonProperty("secondary_key") String secondaryKey)
{
super(primaryKey);
this.secondaryKey = secondaryKey;
}
}
public class X
{
#JsonProperty("keys")
public final A keys;
#JsonCreator
X(#JsonProperty("keys") A keys)
{
this.keys = keys;
}
}
How can I use Jackson Polymorphic feature in order to correctly deserialize the below given json into their respective classes:
JSON A :
{ "keys" :{
"primary_key" : "abc"
}
}
JSON B :
{ "keys" : {
"primary_key" : "abc",
"secondary_key" : "xyz"
}
}
Expected Result: Map keys object to Class A for JSON A and Class B for JSON B.
Please suggest alternative suggestions too.

It feels like a pretty common problem and there is no easy annotations way to solve it (Or maybe i just cant find one):
Jackson Polymorphic Deserialization - Can you require the existence of a field instead of a specific value?
Deserializing polymorphic types with Jackson
One thing you can do is to add custom deserializer to your object mapper. Here is nice demo of this approach: https://stackoverflow.com/a/19464580/1032167
Here is demo related to your example:
import com.fasterxml.jackson.annotation.*;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.ObjectCodec;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.module.SimpleModule;
import java.io.IOException;
public class Main4 {
private static final String jsonA = "{ \"keys\" : { \"primary_key\" : \"abc\" } }";
private static final String jsonB =
"{ \"keys\" : { \"primary_key\" : \"abc\", \"secondary_key\" : \"xyz\" } }";
public static void main(String[] args) throws IOException {
ObjectMapper mapper = new ObjectMapper();
SimpleModule idAsRefModule = new SimpleModule("ID-to-ref");
idAsRefModule.addDeserializer(A.class, new AJsonDeserializer());
mapper.registerModule(idAsRefModule);
X tl = mapper.readValue(jsonA, X.class);
System.out.println(tl);
X t2 = mapper.readValue(jsonB, X.class);
System.out.println(t2);
}
public static class AJsonDeserializer extends JsonDeserializer<A>{
#Override
public A deserialize(JsonParser jp, DeserializationContext dc)
throws IOException {
ObjectCodec codec = jp.getCodec();
JsonNode node = codec.readTree(jp);
if (node.has("secondary_key")) {
return codec.treeToValue(node, B.class);
}
return new A(node.findValue("primary_key").asText());
}
}
public static class A
{
#JsonProperty("primary_key")
public final String primaryKey;
#JsonCreator
A(#JsonProperty("primary_key") String primaryKey)
{
this.primaryKey = primaryKey;
}
#Override
public String toString() {
return "A{" +
"primaryKey='" + primaryKey + '\'' +
'}';
}
}
public static class B extends A
{
#JsonProperty("secondary_key")
public final String secondaryKey;
#JsonCreator
B(#JsonProperty("primary_key") String primaryKey,
#JsonProperty("secondary_key") String secondaryKey)
{
super(primaryKey);
this.secondaryKey = secondaryKey;
}
#Override
public String toString() {
return "B{" +
"primaryKey='" + primaryKey + '\'' +
"secondaryKey='" + secondaryKey + '\'' +
'}';
}
}
public static class X
{
#JsonProperty("keys")
public final A keys;
#JsonCreator
X(#JsonProperty("keys") A keys)
{
this.keys = keys;
}
#Override
public String toString() {
return "X{" +
"keys=" + keys +
'}';
}
}
}
But you will have to create one more super class if you want to use default A deserializer or look here how you can solve this: https://stackoverflow.com/a/18405958/1032167

If I understoon correctly, simply passing the values will work, without any config. I believe this is what you are looking for:
public class Test {
private static final String JSON = "{\"keys\":{\"primary_key\":\"abc\",\"secondary_key\":\"xyz\"}}";
public static void main(String[] args) throws IOException {
ObjectMapper mapper = new ObjectMapper();
X x = mapper.readValue(JSON, X.class);
System.out.println(mapper.writeValueAsString(x));
}
}
class A {
private String primary_key;
public String getPrimary_key() {
return primary_key;
}
public void setPrimary_key(String primary_key) {
this.primary_key = primary_key;
}
}
class B extends A {
private String secondary_key;
public String getSecondary_key() {
return secondary_key;
}
public void setSecondary_key(String secondary_key) {
this.secondary_key = secondary_key;
}
}
class X {
private B keys;
public B getKeys() {
return keys;
}
public void setKeys(B keys) {
this.keys = keys;
}
}
Output will be:
{"keys":{"primary_key":"abc","secondary_key":"xyz"}}
In case this is not what you expect, please provide another explanation and I will edit the answer as needed.

Related

Jackson ObjectMapper Not Reading Inner Objects

I have a JSON file
{
"readServiceAuthorizationResponse": {
"serviceAuthorization": {
"serviceAuthorizationId": "50043~220106065198",
"status": "Approved",
"receivedDate": "2022-1-6 1:21:12 PM",
"providerFirstName": "Ranga",
"providerLastName": "Thalluri",
"organizationName": "General Hospital",
"serviceLines": [{
"statusReason": "Approved",
"procedureDescription": "Room & board ward general classification",
"requestedQuantity": "1.00",
"approvedQuantity": "1.00",
"deniedQuantity": "",
"quantityUnitOfMeasure": "Day(s)",
"providers": [{
"providerFirstName": "Ranga",
"providerLastName": "Thalluri",
"organizationName": ""
}]
}]
}
}
}
My Java to read this into an object is this:
package com.shawn.dto;
import java.nio.file.Files;
import java.nio.file.Paths;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.databind.ObjectMapper;
#JsonIgnoreProperties(ignoreUnknown = true)
public class ServiceAuthorizationDTO {
public String serviceAuthorizationId;
public String status;
public String receivedDate;
public String providerFirstName;
public String providerLastName;
public String organizationName;
public ServiceLine[] serviceLines;
public static ServiceAuthorizationDTO create(String json) {
ObjectMapper m = new ObjectMapper();
try {
Outer outer = m.readValue(json, Outer.class);
return outer.readServiceAuthorizationResponse.serviceAuthorization;
} catch (Exception e) {
return null;
}
}
#JsonIgnoreProperties(ignoreUnknown = true)
static class ReadServiceAuthorizationResponse {
public ServiceAuthorizationDTO serviceAuthorization;
}
#JsonIgnoreProperties(ignoreUnknown = true)
static class Outer {
public ReadServiceAuthorizationResponse readServiceAuthorizationResponse;
}
#JsonIgnoreProperties(ignoreUnknown = true)
public static class ServiceLine {
String statusReason;
String procedureDescription;
String requestedQuantity;
String approvedQuantity;
String deniedQuantity;
String quantityUnitOfMeasure;
Provider[] providers;
}
#JsonIgnoreProperties(ignoreUnknown = true)
public static class Provider {
String providerFirstName;
String providerLastName;
String organizationName;
}
public static void main(String[] args) {
try {
String json = new String(Files.readAllBytes(Paths.get("c:/temp/test.json")));
ServiceAuthorizationDTO dao = ServiceAuthorizationDTO.create(json);
System.out.println("serviceAuthorizationId: " + dao.serviceAuthorizationId);
System.out.println("serviceLines[0].procedureDescription: " + dao.serviceLines[0].procedureDescription);
} catch (Exception e) {
System.out.println(e.getMessage());
}
}
}
When I run it I get this:
serviceAuthorizationId: 50043~220106065198
serviceLines[0].procedureDescription: null
The outer fields in the object like providerId are read from the JSON. But the serviceLines array shows 1 element, and all fields in that class are empty.
Any ideas? This is the first time I've used real objects with JSON. I've always mapped it into Map objects and pulled the fields out manually. Thanks.
Fields in classes ServiceLine and Provider have package-private access modifiers. Jackson can't deserialize into private fields with its default settings. Because it needs getter or setter methods.
Solution 1: Make fields public
public static class ServiceLine {
public String statusReason;
public String procedureDescription;
public String requestedQuantity;
public String approvedQuantity;
public String deniedQuantity;
public String quantityUnitOfMeasure;
public Provider[] providers;
}
Solution 2: Use #JsonAutoDetect annotation
#JsonIgnoreProperties(ignoreUnknown = true)
#JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY)
public class ServiceLine {
String statusReason;
String procedureDescription;
String requestedQuantity;
String approvedQuantity;
String deniedQuantity;
String quantityUnitOfMeasure;
Provider[] providers;
}
Solution 3: Change visibility on the ObjectMapper (doc)
public static ServiceAuthorizationDTO create(String json) {
ObjectMapper objectMapper = new ObjectMapper();
try {
objectMapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY);
Outer outer = objectMapper.readValue(json, Outer.class);
return outer.readServiceAuthorizationResponse.serviceAuthorization;
} catch (Exception e) {
return null;
}
}

Jackson read-only property in method

I need to exposes a property in my json that will be processed in the getter method.
The class:
public class Configuracao{
private String departamento;
public String getDepartamento(){/**getter code**/}
public void setDepartamento(String departamento){/**setter code**/}
public String getDepartamentos(){/***Some logic code***/}
}
The json that got in front: {departamento: "Lote", departamentos: "Lotes"}
Works fine in serialization, but when my front-end post the json back, jackson throws a unrecognized field exception caused by 'departamentos'. How can I tell that I just want to 'departamentos' be serialized by the method value and be ignored in deserialization. I tried #JsonIgnoreProperty, #JsonGetter and #JsonProperty(access = JsonProperty.Access.READ_ONLY) on the method but nothing works.
You can use JsonIgnoreProperties annotation:
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.concurrent.ThreadLocalRandom;
public class JsonPathApp {
public static void main(String[] args) throws Exception {
Configuracao c = new Configuracao();
c.setDepartamento("D1");
ObjectMapper mapper = new ObjectMapper();
String json = mapper.writeValueAsString(c);
System.out.println(json);
System.out.println(mapper.readValue(json, Configuracao.class));
}
}
#JsonIgnoreProperties(ignoreUnknown = true)
class Configuracao {
private String departamento;
public String getDepartamento() {
return departamento;
}
public void setDepartamento(String departamento) {
this.departamento = departamento;
}
public String getDepartamentos() {
return departamento + " " + ThreadLocalRandom.current().nextDouble();
}
#Override
public String toString() {
return "Configuracao{" +
"departamento='" + departamento + '\'' +
'}';
}
}
Above code prints:
{"departamento":"D1","departamentos":"D1 0.8600092703789755"}
Configuracao{departamento='D1'}
JsonProperty.Access.READ_ONLY should also works:
class Configuracao {
private String departamento;
public String getDepartamento() {
return departamento;
}
public void setDepartamento(String departamento) {
this.departamento = departamento;
}
#JsonProperty(access = JsonProperty.Access.READ_ONLY)
public String getDepartamentos() {
return departamento + " " + ThreadLocalRandom.current().nextDouble();
}
#Override
public String toString() {
return "Configuracao{" +
"departamento='" + departamento + '\'' +
'}';
}
}
with above test works as expected.
If you have more classes like this and fields to ignore, you can disable globally feature DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES:
ObjectMapper mapper = new ObjectMapper();
mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
Everything was tested with version 2.9.9
Just define departamentos property in Configuracao class.
public class Configuracao{
private String departamento;
private String departamentos;
//omitted getter/setter
}

Java JSON Processing

I'm having a hard time processing the below JSON with Java, which is being returned from on an external Ansible playbook:
{"Sample":
{
"tag_description":"abc","tag_category_id":"def","tag_id":"ghi"
},
"Sample1":
{
"tag_description":"jkl","tag_category_id":"mno","tag_id":"pqr"
}
}
I've been able to successfully parse one section of the JSON using a custom deserializer, though it only ever gets the first section. Any ideas are hugely appreciated.
#JsonComponent
public class TagSerializer extends JsonDeserializer<Tag> {
#Override
public Tag deserialize(JsonParser jsonParser,
DeserializationContext deserializationContext) throws IOException,
JsonProcessingException {
ObjectMapper mapper = new ObjectMapper();
JsonFactory factory = mapper.getFactory();
JsonNode treeNode = jsonParser.getCodec().readTree(jsonParser);
Iterator<Map.Entry<String, JsonNode>> fields = treeNode.fields();
String name = "";
// collect the tag name
Map.Entry<String, JsonNode> entry = fields.next();
name = entry.getKey();
// now that we have the tag name, parse it as a separate JSON object
JsonNode node = entry.getValue();
// get the values from the JSON
String description = node.get("tag_description").asText();
String category_id = node.get("tag_category_id").asText();
String tag_id = node.get("tag_id").asText();
return new Tag(name, category_id, description, tag_id);
}
}
I'm calling the method from a Spring Boot REST API endpoint, and my 'tag' model is a Spring entity:
'Tag' model:
#Entity
#JsonDeserialize(using = TagSerializer.class)
public class Tag {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
private String name;
private String tag_category_id;
private String tag_description;
private String tag_id;
//JPA requires that a default constructor exists
//for entities
protected Tag() {}
public Tag(String name,
String tag_category_id,
String tag_description,
String tag_id) {
this.name = name;
this.tag_category_id = tag_category_id;
this.tag_description = tag_description;
this.tag_id = tag_id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getTag_category_id() {
return tag_category_id;
}
public void setTag_category_id(String tag_category_id) {
this.tag_category_id = tag_category_id;
}
public String getTag_description() {
return tag_description;
}
public void setTag_description(String tag_description) {
this.tag_description = tag_description;
}
public String getTag_id() {
return tag_id;
}
public void setTag_id(String tag_id) {
this.tag_id = tag_id;
}
public String toString() {
return "<Tag:[Name: " + this.name + "],[tag_category: "+
this.tag_category_id + "],[tag_description: "+
this.tag_description + "],[tag_id:"+this.tag_id+"]";
}
}
Spring Boot endpoint:
#PostMapping(value="/store", consumes = APPLICATION_JSON_VALUE)
public void tagJson(#RequestBody String json) {
// delete any existing tags
tagRepository.deleteAll();
//lets modify the json to make it look nicer
String modjson = "["+json+"]";
ObjectMapper mapper = new ObjectMapper();
try {
Tag[] tags = mapper.readValue(modjson, Tag[].class);
for (Tag t : tags)
tagRepository.save(t);
} catch (Exception exception) {
exception.printStackTrace();
}
}
If you are using Spring MVC consider explicitly declare desired type when referreing to #RequestBody and let the framework do the job for you
#PostMapping(value="/store", consumes = APPLICATION_JSON_VALUE)
public void tagJson(#RequestBody Map<String, Tag> json) {
// Do not mess with ObjectMapper here, Spring will do the thing for you
}
This isn't a direct answer but a guide in a possible direction, using Gson.
package test;
import java.util.Map;
import com.google.gson.Gson;
public class JsonTest {
public static void main(final String... args) {
new JsonTest().run();
}
public void run() {
final Gson gson = new Gson();
final Map<?, ?> result = gson.fromJson("{" +
" \"Sample\": {" +
" \"tag_description\": \"abc\"," +
" \"tag_category_id\": \"def\"," +
" \"tag_id\": \"ghi\"" +
" }," +
" \"Sample1\": {" +
" \"tag_description\": \"jkl\"," +
" \"tag_category_id\": \"mno\"," +
" \"tag_id\": \"pqr\"" +
" }" +
"}", Map.class);
System.out.println("Map size: " + result.size());
}
}
The resulting size is 2. The map entries are keyed Sample, Sample1, and the values are lists containing the nodes. You can see this using a debugger.

Jackson - parse different model under same key at runtime

I have a specific json response from server, where under a key the content would be of different models also at a time only one of the model data would be present under the key.
While parsing the response into POJO how can I specify object type at runtime based on other field of contentType on same model.
Following is the code for better understanding of scenario.
Here content_type is type A and so under "content" key there would be model for object of class TypeA
"scheduled_content": {
"some_field": "value",
"content_type": "typeA",
"content" : {
"some_field" : "value"
"more_feilds" : "value"
}
}
Here content_type is type B and so under "content" key there would be model for object of class TypeB
"scheduled_content": {
"some_field": "value",
"content_type": "typeB",
"content" : {
"some_field_b" : "value"
"more_fields_for_b" : "value"
}
}
How can I write POJO classes to parse such json response?
The type classes are completely different models they don't have any field in common.
I believe that what you are looking for is called, in Jackson JSON terms, polymorphic deserialization by property name.
Here is how I do it with Jackson 2.1.4:
First create an abstract class ScheduledContent with common members and an abstract method that would operate on the content. Use the JsonTypeInfo annotation to mark the JSON property that would resolve the specific implementation and the JsonSubTypes annotation to register the subtypes by the values of the property previously specified:
import com.fasterxml.jackson.annotation.JsonSetter;
import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "content_type")
#JsonSubTypes({
#JsonSubTypes.Type(name = "typeA", value = ScheduledAContent.class),
#JsonSubTypes.Type(name = "typeB", value = ScheduledBContent.class)
})
public abstract class ScheduledContent {
private String someField;
#JsonSetter("some_field")
public void setSomeField(String someField) {
this.someField = someField;
}
public abstract void doSomethingWithContent();
}
The subtypes registration can also be done on the ObjectMapper as you will see later.
Then add the specific implementation for the ScheduledAContent class:
public class ScheduledAContent extends ScheduledContent {
private TypeAContent content;
public void setContent(TypeAContent content) {
this.content = content;
}
#Override
public void doSomethingWithContent() {
System.out.println("someField: " + content.getSomeField());
System.out.println("anotherField: " + content.getAnotherField());
}
}
with TypeAContent being:
import com.fasterxml.jackson.annotation.JsonSetter;
public class TypeAContent {
private String someField;
private String anotherField;
#JsonSetter("some_field")
public void setSomeField(String someField) {
this.someField = someField;
}
public String getSomeField() {
return someField;
}
#JsonSetter("another_field")
public void setAnotherField(String anotherField) {
this.anotherField = anotherField;
}
public String getAnotherField() {
return anotherField;
}
}
and also for the ScheduledBContent class:
public class ScheduledBContent extends ScheduledContent {
private TypeBContent content;
public void setContent(TypeBContent content) {
this.content = content;
}
#Override
public void doSomethingWithContent() {
System.out.println("someField: " + content.getSomeField());
System.out.println("anotherField: " + content.getAnotherField());
}
}
with TypeBContent being:
import com.fasterxml.jackson.annotation.JsonSetter;
public class TypeBContent {
private String someField;
private String anotherField;
#JsonSetter("some_field_b")
public void setSomeField(String someField) {
this.someField = someField;
}
public String getSomeField() {
return someField;
}
#JsonSetter("another_field_b")
public void setAnotherField(String anotherField) {
this.anotherField = anotherField;
}
public String getAnotherField() {
return anotherField;
}
}
And a simple Test class:
import java.io.IOException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.jsontype.NamedType;
public class Test {
public static void main(String[] args) {
String jsonA = "{" +
"\"some_field\": \"main_some_field1\"," +
"\"content_type\": \"typeA\"," +
"\"content\" : {" +
" \"some_field\" : \"content_some_field\"," +
" \"another_field\" : \"content_another_field\"" +
"}}";
String jsonB = "{" +
"\"some_field\": \"main_some_field2\"," +
"\"content_type\": \"typeB\"," +
"\"content\" : {" +
" \"some_field_b\" : \"content_some_field_b\"," +
" \"another_field_b\" : \"content_another_field_b\"" +
"}}";
ObjectMapper mapper = new ObjectMapper();
/*
* This is another way to register the subTypes if you want to do it dynamically without the use of the
* JsonSubTypes annotation in the ScheduledContent class
*/
// mapper.registerSubtypes(new NamedType(ScheduledAContent.class, "typeA"));
// mapper.registerSubtypes(new NamedType(ScheduledBContent.class, "typeB"));
try {
ScheduledContent scheduledAContent = mapper.readValue(jsonA, ScheduledContent.class);
scheduledAContent.doSomethingWithContent();
ScheduledContent scheduledBContent = mapper.readValue(jsonB, ScheduledContent.class);
scheduledBContent.doSomethingWithContent();
} catch (IOException e) {
e.printStackTrace();
}
}
}
that will produce the output:
someField: content_some_field
anotherField: content_another_field
someField: content_some_field_b
anotherField: content_another_field_b
Using #JsonSetter in the setter methods may help. But in this case you will need to create setter methods for each type of fields in "content".
#JsonSetter("some_field")
public void setSomeField1(String field1) {
this.field1 = field1;
}
#JsonSetter("some_field_b")
public void setSomeField2(String field2) {
this.field1 = field1;
}

Parsing dynamically generated JSON object names with Jackson

I'm attempting to deserialize some MediaWiki context from JSON using Jackson into POJOs. However, the problem is that one of the JSON object names is the integer ID value of the article, so using an annotation like #JsonProperty can't be used because the value is never constant.
Here's some sample JSON to describe what I mean:
http://en.wikipedia.org/w/api.php?action=query&titles=Albert%20Einstein&prop=info&format=json&indexpageids
{
"query": {
"pageids": [
"736"
],
"pages": {
"736": {
"pageid": 736,
"ns": 0,
"title": "Albert Einstein",
"contentmodel": "wikitext",
"pagelanguage": "en",
"touched": "2014-01-05T03:14:23Z",
"lastrevid": 588780054,
"counter": "",
"length": 106159
}
}
}
}
(MediaWiki recommends adding the &indexpageids parameter to assist with parsing, however I can't see how it would be useful to me.)
I tried using the #JsonAnyGetter and #JsonAnySetter annotations as well but they don't appear to help, throwing the same exception com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field "736" (class WikipediaPages), not marked as ignorable (one known property: "wikipediaPage"]).
Thanks for any and all assistance.
Edit: Here's what the relevant classes look like at the moment:
public class WikipediaPages {
private Map<String, WikipediaPage> wikipediaPageMap = new HashMap<String, WikipediaPage>();
public Map<String, WikipediaPage> getWikipediaPageMap() {
return wikipediaPageMap;
}
public void setWikipediaPageMap(Map<String, WikipediaPage> wikipediaPageMap) {
this.wikipediaPageMap = wikipediaPageMap;
}
}
I use a Jackson Mixin to apply annotations:
public interface WikipediaPagesMixIn {
#JsonAnyGetter
Map<String, WikipediaPage> getWikipediaPageMap();
#JsonAnySetter
void setWikipediaPageMap(Map<String, WikipediaPage> wikipediaPageMap);
}
Edit 2: More code, as requested:
public class JacksonBuilder {
private static ObjectMapper objectMapper;
public static ObjectMapper getObjectMapper() {
if(objectMapper == null) {
objectMapper = new ObjectMapper();
objectMapper.registerModule(new WikipediaModule());
}
return objectMapper;
}
}
public class WikipediaModule extends SimpleModule {
public WikipediaModule() {
super("WikipediaModule", new Version(1, 0, 0, null, "net.ryanmorrison", "sentience"));
}
#Override
public void setupModule(SetupContext setupContext) {
setupContext.setMixInAnnotations(WikipediaPage.class, WikipediaPageMixIn.class);
setupContext.setMixInAnnotations(WikipediaPages.class, WikipediaPagesMixIn.class);
setupContext.setMixInAnnotations(WikipediaQuery.class, WikipediaQueryMixIn.class);
setupContext.setMixInAnnotations(WikipediaResult.class, WikipediaResultMixIn.class);
}
}
public class WikipediaResult {
private WikipediaQuery wikipediaQuery;
public WikipediaQuery getWikipediaQuery() {
return wikipediaQuery;
}
public void setWikipediaQuery(WikipediaQuery wikipediaQuery) {
this.wikipediaQuery = wikipediaQuery;
}
}
public interface WikipediaResultMixIn {
#JsonProperty("query")
WikipediaQuery getWikipediaQuery();
}
To answer the root cause of your exception, the #JsonAnySetter javadoc states
Marker annotation that can be used to define a non-static,
two-argument method (first argument name of property, second value to
set), [...]
As such, using a mixin like this
#JsonAnySetter
void setWikipediaPageMap(Map<String, WikipediaPage> wikipediaPageMap);
doesn't register it and therefore the property isn't found.
Honestly, don't use mixins if you control the data classes. You can directly map the fields as I've shown below.
I don't know how you are using your mixin, but the following works for me
String json = "{ \"query\": { \"pageids\": [ \"736\" ], \"pages\": { \"736\": { \"pageid\": 736, \"ns\": 0, \"title\": \"Albert Einstein\", \"contentmodel\": \"wikitext\", \"pagelanguage\": \"en\", \"touched\": \"2014-01-05T03:14:23Z\", \"lastrevid\": 588780054, \"counter\": \"\", \"length\": 106159 } } } }";
ObjectMapper mapper = new ObjectMapper();
JsonNode node =mapper.readTree(json);
node = node.get("query").get("pages");
Map<String, Page> pages = mapper.readValue(node.traverse(), new TypeReference<Map<String, Page>>() {
});
System.out.println(pages);
prints
{736=Page [pageid=736, ns=0, title=Albert Einstein, contentmodel=wikitext, pagelanguage=en, touched=2014-01-05T03:14:23Z, lastrevid=588780054, counter=, length=106159]}
Where Page is
class Page {
private int pageid;
private int ns;
private String title;
private String contentmodel;
private String pagelanguage;
private String touched; // this could be a Date, with the appropriate format configuration
private int lastrevid;
private String counter;
private int length;
#Override
public String toString() {
return "Page [pageid=" + pageid + ", ns=" + ns + ", title=" + title
+ ", contentmodel=" + contentmodel + ", pagelanguage="
+ pagelanguage + ", touched=" + touched + ", lastrevid="
+ lastrevid + ", counter=" + counter + ", length=" + length
+ "]";
}
public int getPageid() {
return pageid;
}
public void setPageid(int pageid) {
this.pageid = pageid;
}
public int getNs() {
return ns;
}
public void setNs(int ns) {
this.ns = ns;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getContentmodel() {
return contentmodel;
}
public void setContentmodel(String contentmodel) {
this.contentmodel = contentmodel;
}
public String getPagelanguage() {
return pagelanguage;
}
public void setPagelanguage(String pagelanguage) {
this.pagelanguage = pagelanguage;
}
public String getTouched() {
return touched;
}
public void setTouched(String touched) {
this.touched = touched;
}
public int getLastrevid() {
return lastrevid;
}
public void setLastrevid(int lastrevid) {
this.lastrevid = lastrevid;
}
public String getCounter() {
return counter;
}
public void setCounter(String counter) {
this.counter = counter;
}
public int getLength() {
return length;
}
public void setLength(int length) {
this.length = length;
}
}
All that is left is to put the Map<String, Page> as a field in some wrapper class for the query and pages JSON elements.

Categories