How to model hierarchical json in java - java

I'm a front end developer who is brand new to backend development. My task is to model json in a Java object. It's just some mock data for now that my controller returns.
{
"data":{
"objectId":25,
"columnName":[
"myCategory",
"myCategoryId"
],
"columnValues":[
[
"Category One",
1
],
[
"Category Two",
2
],
[
"Category Three",
3
],
[
"Category Four",
4
],
[
"Category Five",
5
]
]
}
}
And here's my attempt. The controller returns this json correctly. But isn't this too simple? What I believe should be done is extrapolate the columnName and columnValues arrays into separate classes but I'm not sure how.
package com.category;
import java.util.List;
public class MyObjectData {
private int objectId;
private List columnName;
private List columnValues;
public int getObjectId() {
return objectId;
}
public void setObjectId(int objectId) {
this.objectId = objectId;
}
public List getColumnName() {
return columnName;
}
public void setColumnName(List colName) {
this.columnName = colName;
}
public List getColumnValues() {
return columnValues;
}
public void setValues(List values) {
this.columnValues = values;
}
}
Regarding the columnNames and columnValues, I feel like I should be doing something like this in the model instead:
private List<ColumnNames> columnNames;
private List<ColumnValues> columnValues;
public List<ColumnNames> getColumnNames() {
return columnNames;
}
public void setColumnNames(List<ColumnNames> columnNames) {
this.columnNames = columnNames;
}
public List<ColumnValues> getColumnValues() {
return columnValues;
}
public void setColumnValues(List<ColumnValues> columnValues) {
this.columnValues = columnValues;
}
And then I'd have two separate classes for them like this:
package com.category;
import java.util.List;
public class ColumnName {
private String columnName;
public String getColumnName() {
return columnName;
}
public void setColumnName(String columnName) {
this.columnName = columnName;
}
}
package com.category;
import java.util.List;
public class ColumnValue {
private String columnValue;
private int columnValueId;
public String getColumnValue() {
return columnValue;
}
public void setColumnValue(String columnValue) {
this.columnValue = columnValue;
}
public String getColumnValueId() {
return columnValueId;
}
public void setColumnValueId(int columnValueId) {
this.columnValueId = columnValueId;
}
}
I feel like I have all the right pieces but just not sure if this is a better approach than my initial attempt...which works. Just looking for input. Thanks in advance.

In your structure, columnValues is actually the rows of your table that has two columns: myCategory and myCategoryId.
A more "object oriented" Java class could be something like this instead:
public class MyObjectData {
private int objectId;
private List<MyObjectRow> columnValues; // I would have named this as rows
}
public class MyObjectRow {
private String myCategory;
private String myCategoryId;
}
Now you need a custom serializer to turn this into your expected JSON structure:
public class MyObjectDataSerializer extends StdSerializer<MyObjectData> {
public MyObjectDataSerializer() {
super(MyObjectData.class);
}
public void serialize(MyObjectData value, JsonGenerator generator, SerializerProvider provider) throws IOException {
generator.writeStartObject();
generator.writeNumberField("objectId", value.getObjectId());
generator.writeArrayFieldStart("columnName");
generator.writeString("myCategory");
generator.writeString("myCategoryId");
generator.writeEndArray();
generator.writeArrayFieldStart("columnValues");
for (MyObjectRow row : value.getColumnValues()) {
generator.writeStartArray();
generator.writeString(row.getMyCategory());
generator.writeNumber(row.getMyCategoryId());
generator.writeEndArray();
}
generator.writeEndArray();
generator.writeEndObject();
}
}
Note: You can use reflection to extract the field names and values dynamically.
Then you can serialize MyObjectData objects into your expected form:
public class MyObjectDataSerializerTest {
#Test
public void shouldCustomSerializeMyObjectData() throws Exception {
ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
module.addSerializer(MyObjectData.class, new MyObjectDataSerializer());
mapper.registerModule(module);
MyObjectData myObjectData = new MyObjectData();
myObjectData.setObjectId(25);
myObjectData.setColumnValues(Arrays.asList(
new MyObjectRow("Category One", 1),
new MyObjectRow("Category Two", 2),
new MyObjectRow("Category Three", 3)
));
String serialized = mapper.writeValueAsString(myObjectData);
assertThat(serialized, equalTo("{\"objectId\":25,\"columnName\":[\"myCategory\",\"myCategoryId\"],\"columnValues\":[[\"Category One\",1],[\"Category Two\",2],[\"Category Three\",3]]}\n"));
}
}

Related

How to parse complex nested JSON in java?

I have a complex nested Json
It has a body similar to this:
{
staus: "Success",
id: 1,
data: [{'Movie':'kung fu panda','% viewed': 50.5},{'Movie':'kung fu panda 2','% viewed':1.5}],
metadata: {'filters':['Movie', 'Percentage Viewed'], 'params':{'content':'Comedy', 'type': 'Movie'}}
}
The only field I care about is data, and metadata is usually an even more complex/nested field. I was trying to map this to:
#JsonIgnoreProperties(ignoreUnknown = true)
class ResponseData{
public Data[] data;
#JsonIgnoreProperties(ignoreUnknown = true)
class Data{
public String Movie;
public double viewed;
}
}
I was looking at Jackson as an option and writing my own serializer and use JsonIgnore to discard the metadata but can't get around it.
Any suggestion on how this could be done?
You can use jackson-utils
public class Foo {
public static void main(String... args) {
ResponseData responseData1 = new ResponseData(
1,
"Success",
new ResponseData.Data[] {
new ResponseData.Data("kung fu panda", 50.5),
new ResponseData.Data("kung fu panda 2", 1.5) },
new ResponseData.Metadata(
new HashSet<>(Arrays.asList("Movie", "Percentage Viewed")),
new ResponseData.Metadata.Params("Comedy", "Movie"))
);
String json = JacksonUtils.prettyPrint().writeValue(responseData1);
System.out.println(json);
ResponseData responseData2 = JacksonUtils.readValue(json, ResponseData.class);
}
}
class ResponseData {
private int id;
private String status;
private Data[] data;
private Metadata metadata;
public ResponseData() {
}
public ResponseData(int id, String status, Data[] data, Metadata metadata) {
this.id = id;
this.status = status;
this.data = data;
this.metadata = metadata;
}
public static class Data {
#JsonProperty("Movie")
private String movie;
#JsonProperty("% viewed")
private double viewedPercents;
public Data() {
}
public Data(String movie, double viewedPercents) {
this.movie = movie;
this.viewedPercents = viewedPercents;
}
}
public static class Metadata {
private Set<String> filters;
private Params params;
public Metadata() {
}
public Metadata(Set<String> filters, Params params) {
this.filters = filters;
this.params = params;
}
public static class Params {
private String content;
private String type;
public Params() {
}
public Params(String content, String type) {
this.content = content;
this.type = type;
}
}
}
}
Console output:
{
"id" : 1,
"status" : "Success",
"data" : [ {
"Movie" : "kung fu panda",
"% viewed" : 50.5
}, {
"Movie" : "kung fu panda 2",
"% viewed" : 1.5
} ],
"metadata" : {
"filters" : [ "Movie", "Percentage Viewed" ],
"params" : {
"content" : "Comedy",
"type" : "Movie"
}
}
}
P.S. As an alternative, there is another util gson-utils with the same syntax.

How to convert a POST API call with a JSON body to a java class in Springboot?

i'm passing a POST request with raw JSON body which holds the configuration details, i want to save it as a java class so i can distribute the data using getters to relevant classes that need the config details specified for them.This is the body of my request
{
"aisles" : 2,
"sections" : 2,
"shelves" : 1,
"packagingAreas": [ "a1.3", "a2.3" ],
"workers" : [
{
"name" : "rem",
"location" : "a1.1",
"capacity" : 20
}
],
"items" : [
{
"id" : "mars",
"name" : "Mars",
"supplier" : "Nestle",
"weight" : 1
},
{
"id" : "kitkat",
"name" : "Kit Kat",
"supplier" : "Nestle",
"weight" : 1
},
{
"id" : "dd",
"name" : "Double Decker",
"supplier" : "Nestle",
"weight" : 1
}
]
}
and i want to the details of that body into my config.java class, this is the config.java class
public class Config {
private static String aisles;
private static String sections;
private static String shelves;
private static String packagingAreas[];
private static ArrayList<Worker> workers;
private static ArrayList<Item> items;
public static String getAisles() {
return aisles;
}
public static String getSections() {
return sections;
}
public static String getShelves() {
return shelves;
}
public static String[] getPackagingAreas() {
return packagingAreas;
}
public static ArrayList<Worker> getWorkers() {
return workers;
}
public static ArrayList<Item> getItems() {
return items;
}
}
And i have modelled the worker and item classes with the same variables as in the json configuration file, is there a direct way to convert this JSON file to a class? if not what other methods can i try?
Thanks in advance!
edit- This is the endpoint i have created, using #Rest Controller
#RequestMapping(value ="/config", method = RequestMethod.POST)
public void configure() {
//i want to do the conversion here
}
Update the controller as follows
#RequestMapping(value ="/config", method = RequestMethod.POST)
public void configure(#RequestBody Config config) {
//your json is converted to config java object
}
Update your Config class
public class Config {
private String aisles;
private String sections;
private String shelves;
private String packagingAreas[];
private ArrayList<Worker> workers;
private ArrayList<Item> items;
public void setAisles(String aisles) {
this.aisles = aisles;
}
public void setSections(String sections) {
this.sections = sections;
}
public void setShelves(String shelves) {
this.shelves = shelves;
}
public void setPackagingAreas(String[] packagingAreas) {
this.packagingAreas = packagingAreas;
}
public void setWorkers(ArrayList<Worker> workers) {
this.workers = workers;
}
public void setItems(ArrayList<Item> items) {
this.items = items;
}
public String getAisles() {
return aisles;
}
public String getSections() {
return sections;
}
public String getShelves() {
return shelves;
}
public String[] getPackagingAreas() {
return packagingAreas;
}
public ArrayList<Worker> getWorkers() {
return workers;
}
public ArrayList<Item> getItems() {
return items;
}
}
Explanation
By default spring boot comes with several HttpMessageConverters enabled. One of them is MappingJacksonHttpMessageConverter which converts your json to java object.
See this http message converter

Infinite loop when saving multiple entities with a post-method in Spring boot

To explain the problem I'm dealing with I will first provide the code.
RecipeController
#RequestMapping(path = "/addrecipe")
public void addNewRecipe(#RequestBody AddRecipeDto addRecipeDto){
Recipe newRecipe = new Recipe();
EvaUser user = evaUserRepository.findOne(addRecipeDto.getUserId());
for(Ingredient ingredient: addRecipeDto.getIngredients()){
ingredientRepository.save(ingredient);
}
newRecipe.setTitle(addRecipeDto.getTitle());
newRecipe.setAuthor(user);
newRecipe.setDescription(addRecipeDto.getDescription());
newRecipe.setIngredients(addRecipeDto.getIngredients());
recipeRepository.save(newRecipe);
user.getMyRecipes().add(newRecipe);
evaUserRepository.save(user);
}
UserController
#RequestMapping("/getusers")
public Iterable<EvaUser> getAllUsers() {
return evaUserRepository.findAll();
}
EvaUser
#OneToMany
private List<Recipe> myRecipes;
#ManyToMany
private List<Recipe> favoriteRecipes;
Recipe
#ManyToOne
private EvaUser author;
Exception
Failed to write HTTP message:
org.springframework.http.converter.HttpMessageNotWritableException: Could
not write content: Infinite recursion
Problem
So when I call the method to add a recipe, I want the database to know that there is a new recipe and that the new recipe is linked to the user who added it. When I drop the part where I save the user-entity, the mapping isn't made at all. But when I use the userRepository to tell the database that there has been made a change (adding the recipe to their list) it seems like there is an infinite loop of adding new users.
Answering to your question and including the last requirements from your comments.
If you want to break the loop, but some somehow want to keep also nested objects, I would recommend to write a custom serializer and replace the the object which causes the endless recursion with some other field (I used author username which is String instead of Author object in the example below).
To reproduce the case I created a mock model which is similar to yours.
Recipe:
public class Recipe {
private EvaUser author;
private String name = "test";
private String ingridients = "carrots, tomatos";
public EvaUser getAuthor() {
return author;
}
public void setAuthor(EvaUser author) {
this.author = author;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getIngridients() {
return ingridients;
}
public void setIngridients(String ingridients) {
this.ingridients = ingridients;
}
}
EvaUser:
public class EvaUser {
private List<Recipe> myRecipes = new ArrayList<>();
private List<Recipe> favoriteRecipes = new ArrayList<>();
private String username;
public List<Recipe> getMyRecipes() {
return myRecipes;
}
public void setMyRecipes(List<Recipe> myRecipes) {
this.myRecipes = myRecipes;
}
public List<Recipe> getFavoriteRecipes() {
return favoriteRecipes;
}
public void setFavoriteRecipes(List<Recipe> favoriteRecipes) {
this.favoriteRecipes = favoriteRecipes;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
}
Creating a custom serializer:
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
import java.io.IOException;
import java.util.Optional;
public class RecipeSerializer extends StdSerializer<Recipe> {
protected RecipeSerializer() {
this(null);
}
protected RecipeSerializer(Class<Recipe> t) {
super(t);
}
#Override
public void serialize(Recipe recipe, JsonGenerator gen, SerializerProvider provider) throws IOException {
gen.writeStartObject();
gen.writeStringField("name", recipe.getName());
gen.writeStringField("author", Optional.ofNullable(recipe.getAuthor().getUsername()).orElse("null"));
gen.writeStringField("ingridients", recipe.getIngridients());
gen.writeEndObject();
}
}
Applying serializer:
#JsonSerialize(using = RecipeSerializer.class)
public class Recipe {
// model entity
}
JSON response body of EvaUser from controller (previous one was StackOverflowError):
{
"myRecipes": [
{
"name": "soup",
"author": "user1",
"ingridients": "carrots, tomatos"
},
{
"name": "steak",
"author": "user1",
"ingridients": "meat, salt"
}
],
"favoriteRecipes": [
{
"name": "soup",
"author": "user1",
"ingridients": "carrots, tomatos"
},
{
"name": "steak",
"author": "user1",
"ingridients": "meat, salt"
}
],
"username": "user1"
}

JSON data binding with custom logic using Jackson

I have defined JSON response which I want to deserialize into Java Objects. I managed to do it "manually" with the Tree Model but if possible I would like to use Data Binding instead. The problem is that I need some custom logic for some parts.
The JSON looks like this:
{
"resourcedescriptions": [
{
"path": "somePath",
"tag_pagetype": "default",
"tag_bookingcenter": [
"bc_ch",
"bc_de"
],
"resources": [
{
"path": "somePathDe.html",
"lang": "de",
"lastmodified": 1399020442914,
"mimetype": "text/html"
},
{
"path": "somePathEn.html",
"lang": "en",
"lastmodified": 1399907224208,
"mimetype": "text/html"
}
],
"lastmodified": 1399907224208
},
{
"path": "someOtherPath",
"tag_pagetype": "special",
"tag_bookingcenter": [
"bc_ch"
],
"resources": [
{
"path": "someOtherPathDe.html",
"lang": "de",
"lastmodified": 1399020442914,
"mimetype": "text/html"
},
{
"path": "someOtherPathEn.html",
"lang": "en",
"lastmodified": 1399907224208,
"mimetype": "text/html"
}
],
"lastmodified": 1399907224208
}
]
}
My Java Classes would be:
public class ResourceDescription {
private String path;
private LocalDateTime lastModified;
private String chartConfig;
private final List<Tag> tags = new ArrayList<Tag>();
private final List<Resource> resources = new ArrayList<Resource>();
}
public class Resource {
private String lang;
private String path;
private String mimeType;
private LocalDateTime lastModified;
}
public class Tag {
private String namespace;
private String name;
}
First question which I still don't fully understand even with reading many posts here. How do I deserialize this array of Resources from the JSON into my List of the ResourceDescription?
Second and most complex question. The JSON properties prefixed with "tag_" need to be transformed into the Tag class, whereas the the property name represents the namespace and the value (single or array) represent the name. So if the pattern is "namespace:name", the first ResourceDescription would have the following tags:
tag_pagetype:default
tag_bookingcenter:bc_ch
tag_bookingcenter:bc_de
Third the "lastmodified" should be transformed into DateTime from Joda-Time.
Is this even possible with data binding or should I stick to the Tree Model?
How do I deserialize this array of Resources from the JSON into my
List of the ResourceDescription?
You have to create additional root class which contains resourcedescriptions property. For example:
class Root {
private List<ResourceDescription> resourcedescriptions;
public List<ResourceDescription> getResourcedescriptions() {
return resourcedescriptions;
}
public void setResourcedescriptions(List<ResourceDescription> resourcedescriptions) {
this.resourcedescriptions = resourcedescriptions;
}
#Override
public String toString() {
return String.valueOf(resourcedescriptions);
}
}
The JSON properties prefixed with "tag_" need to be transformed into
the Tag class, whereas the the property name represents the namespace
and the value (single or array) represent the name.
You can handle this case using #JsonAnySetter annotation. You have to add new method to ResourceDescription class which could look like this:
#JsonAnySetter
public void setAnyValues(String propertyName, Object value) {
if (propertyName.startsWith("tag_")) {
if (value instanceof String) {
tags.add(new Tag(propertyName, value.toString()));
} else if (value instanceof List) {
List<?> values = (List<?>) value;
for (Object v : values) {
tags.add(new Tag(propertyName, v.toString()));
}
}
// throw exception?
} else {
// handle another unknown properties
}
}
Third the "lastmodified" should be transformed into DateTime from
Joda-Time.
You can handle JodaTime types by adding jackson-datatype-joda library. When you add it you can register JodaModule module.
mapper.registerModule(new JodaModule());
Additional problem that your JSON contain properties written using lowercase, but your POJO properties are written using camel-case. You can change JSON or POJO or use #JsonProperty("property-name-from-JSON") annotation or implement your own naming strategy. For example:
mapper.setPropertyNamingStrategy(new PropertyNamingStrategy.PropertyNamingStrategyBase() {
#Override
public String translate(String propertyName) {
return propertyName.toLowerCase();
}
});
Full Java example how to you can deserialize your JSON:
import java.util.ArrayList;
import java.util.List;
import org.joda.time.LocalDateTime;
import com.fasterxml.jackson.annotation.JsonAnySetter;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.PropertyNamingStrategy;
import com.fasterxml.jackson.datatype.joda.JodaModule;
public class JacksonProgram {
public static void main(String[] args) throws Exception {
String json = "{ ... }";
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new JodaModule());
mapper.setPropertyNamingStrategy(new PropertyNamingStrategy.PropertyNamingStrategyBase() {
#Override
public String translate(String propertyName) {
return propertyName.toLowerCase();
}
});
System.out.println(mapper.readValue(json, Root.class));
}
}
class Root {
private List<ResourceDescription> resourcedescriptions;
public List<ResourceDescription> getResourcedescriptions() {
return resourcedescriptions;
}
public void setResourcedescriptions(List<ResourceDescription> resourcedescriptions) {
this.resourcedescriptions = resourcedescriptions;
}
#Override
public String toString() {
return String.valueOf(resourcedescriptions);
}
}
class ResourceDescription {
private String path;
private LocalDateTime lastModified;
private String chartConfig;
private final List<Tag> tags = new ArrayList<Tag>();
private final List<Resource> resources = new ArrayList<Resource>();
#JsonAnySetter
public void setAnyValues(String propertyName, Object value) {
if (propertyName.startsWith("tag_")) {
if (value instanceof String) {
tags.add(new Tag(propertyName, value.toString()));
} else if (value instanceof List) {
List<?> values = (List<?>) value;
for (Object v : values) {
tags.add(new Tag(propertyName, v.toString()));
}
}
// throw exception?
} else {
// handle another unknown properties
}
}
public String getPath() {
return path;
}
public void setPath(String path) {
this.path = path;
}
public LocalDateTime getLastModified() {
return lastModified;
}
public void setLastModified(LocalDateTime lastModified) {
this.lastModified = lastModified;
}
public String getChartConfig() {
return chartConfig;
}
public void setChartConfig(String chartConfig) {
this.chartConfig = chartConfig;
}
public List<Tag> getTags() {
return tags;
}
public List<Resource> getResources() {
return resources;
}
#Override
public String toString() {
return "ResourceDescription [path=" + path + ", lastModified=" + lastModified
+ ", chartConfig=" + chartConfig + ", tags=" + tags + ", resources=" + resources
+ "]";
}
}
class Resource {
private String lang;
private String path;
private String mimeType;
private LocalDateTime lastModified;
public String getLang() {
return lang;
}
public void setLang(String lang) {
this.lang = lang;
}
public String getPath() {
return path;
}
public void setPath(String path) {
this.path = path;
}
public String getMimeType() {
return mimeType;
}
public void setMimeType(String mimeType) {
this.mimeType = mimeType;
}
public LocalDateTime getLastModified() {
return lastModified;
}
public void setLastModified(LocalDateTime lastModified) {
this.lastModified = lastModified;
}
#Override
public String toString() {
return "Resource [lang=" + lang + ", path=" + path + ", mimeType=" + mimeType
+ ", lastModified=" + lastModified + "]";
}
}
class Tag {
private String namespace;
private String name;
public Tag() {
}
public Tag(String namespace, String name) {
this.namespace = namespace;
this.name = name;
}
public String getNamespace() {
return namespace;
}
public void setNamespace(String namespace) {
this.namespace = namespace;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
#Override
public String toString() {
return "Tag [namespace=" + namespace + ", name=" + name + "]";
}
}
Above program prints:
[ResourceDescription [path=somePath, lastModified=2014-05-12T17:07:04.208, chartConfig=null, tags=[Tag [namespace=tag_pagetype, name=default], Tag [namespace=tag_bookingcenter, name=bc_ch], Tag [namespace=tag_bookingcenter, name=bc_de]], resources=[Resource [lang=de, path=somePathDe.html, mimeType=text/html, lastModified=2014-05-02T10:47:22.914], Resource [lang=en, path=somePathEn.html, mimeType=text/html, lastModified=2014-05-12T17:07:04.208]]], ResourceDescription [path=someOtherPath, lastModified=2014-05-12T17:07:04.208, chartConfig=null, tags=[Tag [namespace=tag_pagetype, name=special], Tag [namespace=tag_bookingcenter, name=bc_ch]], resources=[Resource [lang=de, path=someOtherPathDe.html, mimeType=text/html, lastModified=2014-05-02T10:47:22.914], Resource [lang=en, path=someOtherPathEn.html, mimeType=text/html, lastModified=2014-05-12T17:07:04.208]]]]
You will need to create a custom deserializer for ResourceDescription in order to accomplish what you need to do. The syntax for specifying a custom deserializer for ResourceDescription will look like this:
#JsonDeserialize(using=ResourceDescriptionDeserializer.class)
public class ResourceDescription { ... }
This deserializer will have to iterate through each of the keys for each resource description to see if it begins with "tag_", strip off the prefix and use the remaining for the namespace and populate the name/value for the Tag before adding it to the array of the ResourceDescription being created.
For all other attributes/types I think you can just defer to the default deserialization and set those attributes on their respective fields.
Then, to deserialize the list of ResourceDescriptions you can specify a TypeReference to avoid writing a custom deserializer for ResourceDescriptions. The code will look something like this:
Map<String, List<ResourceDescription>> resultMap =
objectMapper.readValue(JSON, new TypeReference<Map<String, List<ResourceDescription>>>() {});
List<ResourceDescription> descriptions = resultMap.get("resourcedescriptions");
Here's an article that doesn't quite pair with what you're doing but I think will help with the general idea:
Using Jackson to deserialize array nested within array in JSON object

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