I am using Jackson APIs for Mapping my JSON response into a java object.
For example,
for the response { name :'karthikeyan',age:'24',gender:'Male'}
#JsonProperty("name")
public String _name;
#JsonProperty("age")
public int _age;
#JsonProperty("gender")
public String _gender;
is the Mix-in and it works fine.(internally we will be mapping this pojo and Mix-in).Now how can i represent the following response in a Mix-in?
{
name :'karthikeyan',
age:'24',
gender:'Male',
interest:
{
books:'xxx',
music:'yyy',
movie:'zzz'
}
}
i have tried with the following, but no luck.
#JsonProperty("name")
public String _name;
#JsonProperty("age")
public int _age;
#JsonProperty("gender")
public String _gender;
#JsonProperty("interest")
public InterestPojo interestPojo; //created same format mix-in and pojo for interest params as well.
but unable to map them exactly, give your comments and thoughts on how to do it ?
I tried the following:
ObjectMapper mapper = new ObjectMapper();
System.out.println(mapper.writeValueAsString(new Something("Name", 12, "male", new Nested("books", "Music", "Movie"))));
public class Something {
#JsonProperty("name")
public String name;
#JsonProperty("age")
public int age;
#JsonProperty("gender")
public String gender;
#JsonProperty("interest")
public Nested nested;
//Constructor
}
public class Nested {
#JsonProperty("books")
public String books;
#JsonProperty("music")
public String music;
#JsonProperty("movie")
public String movie;
//Constructor
}
and the Output is:
{
"name":"Name",
"age":12,
"gender":"male",
"interest":
{
"books":"books",
"music":"Music",
"movie":"Movie"
}
}
So everything is working as expected. I already checked if theres a difference if you provide some setters and getters and setting the visibility of the fields on private but that doas not make a difference.
Maybe you want to show us your InterestPojo or your output/stacktrace?
EDIT:
Okay i think i got it ;)
I tried the following:
public void start() throws IOException {
ObjectMapper mapper = new ObjectMapper();
mapper.getSerializationConfig().addMixInAnnotations(Something.class, Nested.class);
mapper.getDeserializationConfig().addMixInAnnotations(Something.class, Nested.class);
System.out.println(mapper.writeValueAsString(new Something("Name", 12, "male", new NestedImpl("name", null))));
}
public final class Something {
private final String name;
private int age;
private String gender;
// thats your interest thing
public Nested nested;
public Something(String name, int age, String gender, Nested nested) {
this.name = name;
this.age = age;
this.gender = gender;
this.nested = nested;
}
String getName() {
return name;
}
Nested getNested() {
return nested;
}
}
public abstract class Nested {
#JsonProperty("name-ext")
abstract String getName();
#JsonProperty("interest-ext")
abstract Nested getNested();
}
public class NestedImpl extends Nested {
private String name;
private Nested nested;
private NestedImpl(String name, Nested nested) {
this.name = name;
this.nested = nested;
}
#Override
String getName() {
return name;
}
#Override
Nested getNested() {
return nested;
}
}
Output:
{
"age":12,
"gender":"male",
"name-ext":"Name",
"interest-ext":
{
"name-ext":"name",
"interest-ext":null
}
}
Thats not exactly your structure, but I think thats what you want. Am I right?
EDIT2: I Tested the following structure with JSON->Object and Object->JSON.
ObjectMapper mapper = new ObjectMapper();
mapper.getSerializationConfig().addMixInAnnotations(Something.class, Mixin.class);
mapper.getSerializationConfig().addMixInAnnotations(Nested.class, NestedMixin.class);
mapper.getDeserializationConfig().addMixInAnnotations(Something.class, Mixin.class);
mapper.getDeserializationConfig().addMixInAnnotations(Nested.class, NestedMixin.class);
Nested nested = new Nested();
nested.setName("Nested");
nested.setNumber(12);
Something some = new Something();
some.setName("Something");
some.setAge(24);
some.setGender("Male");
some.setNested(nested);
String json = mapper.writeValueAsString(some);
System.out.println(json);
Something some2 = mapper.readValue(json, Something.class);
System.out.println("Object: " + some2);
public abstract class Mixin {
#JsonProperty("name")
private String _name;
#JsonProperty("age")
private int _age;
#JsonProperty("gender")
private String _gender;
#JsonProperty("interest")
private Nested nested;
}
public class Something {
private String _name;
private int _age;
private String _gender;
private Nested nested;
// You have to provide Setters and Getters!!
}
public abstract class NestedMixin {
#JsonProperty("nameNested")
private String name;
#JsonProperty("numberNested")
private int number;
}
public class Nested {
private String name;
private int number;
// You have to provide Setters and Getters!!
}
Output:
{"age":24,"gender":"Male","name":"Something","interest":{"nameNested":"Nested","numberNested":12}}
Object: Something{name=Something, age=24, gender=Male, nested=Nested{name=Nested, number=12}}
Note: It seems that jackson got problems with inner classes. So if you test that examples in an extra project create extra class-files ;)
EDIT3: If you are using a Module, try the following:
public class JacksonMixinModule extends SimpleModule {
public JacksonMixinModule() {
super("JacksonMixinModule", new Version(0, 1, 0, "SNAPSHOT"));
}
#Override
public void setupModule(SetupContext context) {
super.setupModule(context);
context.setMixInAnnotations(Something.class, Mixin.class);
context.setMixInAnnotations(Nested.class, NestedMixin.class);
}
}
...
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new JacksonMixinModule());
Related
How do I get Jackson to treat 'name' as if it had a #JsonProperty annotation?
public class SimpleClass {
private String name;
private String doNotSerialize;
public SimpleClass( #JsonProperty("name") String name ) {
this.name = name;
}
public String getName() {
return name;
}
public int getSum() {
return 1+1;
}
}
The way it is now, I get an error, Unrecognized field "sum", because it treats every getter as a serializable property.
If I add a class annotation:
#JsonAutoDetect( getterVisibility = JsonAutoDetect.Visibility.NONE )
I get an empty string when serializing. I was hoping that Jackson would see the #JsonProperty on the constructor parameter and figure it out.
If I change the class annotation to:
#JsonAutoDetect( getterVisibility = JsonAutoDetect.Visibility.NONE, fieldVisibility = JsonAutoDetect.Visibility.ANY )
Then I get the 'doNotSerialize' field included.
If I set a #JsonCreator on the constructor, and change my autodetect, I still get a blank string:
#JsonAutoDetect( getterVisibility = JsonAutoDetect.Visibility.NONE, fieldVisibility = JsonAutoDetect.Visibility.NONE, creatorVisibility = JsonAutoDetect.Visibility.ANY )
public class SimpleClass {
private String name;
private String doNotSerialize;
#JsonCreator
public SimpleClass( #JsonProperty("name") String name ) {
this.name = name;
}
public String getName() {
return name;
}
public int getSum() {
return 1+1;
}
}
What I'm hoping is that somehow I can tell Jackson to treat all the constructor parameters as serializable fields, and all other fields / setters as non-serializable.
You can use a filter to only serialise getters which have a matching field, e.g.
package org.example;
import com.fasterxml.jackson.annotation.JsonFilter;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ser.BeanPropertyWriter;
import com.fasterxml.jackson.databind.ser.PropertyWriter;
import com.fasterxml.jackson.databind.ser.impl.SimpleBeanPropertyFilter;
import com.fasterxml.jackson.databind.ser.impl.SimpleFilterProvider;
import java.io.IOException;
import java.io.StringWriter;
public class App {
#JsonFilter("test")
public static class SimpleClass {
private String name;
private String doNotSerialize;
public SimpleClass(String name ) {
this.name = name;
}
public String getName() {
return name;
}
public int getSum() {
return 1+1;
}
}
public static void main( String[] args ) throws IOException {
SimpleFilterProvider filterProvider = new SimpleFilterProvider();
filterProvider.addFilter("test", new SimpleBeanPropertyFilter() {
#Override
protected boolean include(BeanPropertyWriter writer) {
return super.include(writer);
}
#Override
protected boolean include(PropertyWriter writer) {
String name = writer.getName();
Class clazz = writer.getMember().getDeclaringClass();
try {
clazz.getDeclaredField(name);
return super.include(writer);
} catch (NoSuchFieldException e) {
// ignore
return false;
}
}
});
ObjectMapper mapper = new ObjectMapper();
mapper.setFilterProvider(filterProvider);
StringWriter sw = new StringWriter();
mapper.createGenerator(sw).writeObject(new SimpleClass("foo"));
System.out.println(sw.toString());
}
}
I don't know your full requirements, but this should be a start.
I haven't tried to do what you actually, asked, that is, look at constructor parameters, but that should be possible too.
If you want "sum" to be included in the serializad json but want to ignore it when deserializing you can do:
#JsonIgnoreProperties(ignoreUnknown=true)
public class SimpleClass {
// properties/getters
public int getSum() { return 1+1; }
}
If you want to remove "sum" entirely from the json you can do
#JsonIgnoreProperties({"sum"})
public class SimpleClass {
// properties/getters
public int getSum() { return 1+1; }
}
or
public class SimpleClass {
// properties/getters
#JsonIgnore
public int getSum() { return 1+1; }
}
I am trying to parse the JSON result from the Wordpress plugins API using Retrofit2 and GSON. I have generated my POJO using the well known website and modified it into the following model:
PluginsApiResponse.java
public class PluginsApiResponse {
#SerializedName("plugins")
#Expose
private List<Plugin> plugins = null;
public List<Plugin> getPlugins() {
return plugins;
}
public void setPlugins(List<Plugin> plugins) {
this.plugins = plugins;
}
}
Plugin.java
public class Plugin {
#SerializedName("name")
#Expose
private String name;
#SerializedName("homepage")
#Expose
private String homepage;
#SerializedName("screenshots")
#Expose
private Screenshots screenshots;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getHomepage() {
return homepage;
}
public void setHomepage(String homepage) {
this.homepage = homepage;
}
public Screenshots getScreenshots() {
return screenshots;
}
public void setScreenshots(Screenshots screenshots) {
this.screenshots = screenshots;
}
}
Screenshots.java
public class Screenshots {
#SerializedName("1")
#Expose
private com.dkalsan.retrofitwordpress._1 _1;
#SerializedName("2")
#Expose
private com.dkalsan.retrofitwordpress._2 _2;
#SerializedName("3")
#Expose
private com.dkalsan.retrofitwordpress._3 _3;
public com.dkalsan.retrofitwordpress._1 get1() {
return _1;
}
public void set1(com.dkalsan.retrofitwordpress._1 _1) {
this._1 = _1;
}
public com.dkalsan.retrofitwordpress._2 get2() {
return _2;
}
public void set2(com.dkalsan.retrofitwordpress._2 _2) {
this._2 = _2;
}
public com.dkalsan.retrofitwordpress._3 get3() {
return _3;
}
public void set3(com.dkalsan.retrofitwordpress._3 _3) {
this._3 = _3;
}
}
_1.java (_2.java and _3.java are identical)
public class _1 {
#SerializedName("src")
#Expose
private String src;
#SerializedName("caption")
#Expose
private String caption;
public String getSrc() {
return src;
}
public void setSrc(String src) {
this.src = src;
}
public String getCaption() {
return caption;
}
public void setCaption(String caption) {
this.caption = caption;
}
}
The problem occurs in case the screenshots field contains no entries. I've set up the HttpLoggingInterceptor, which logs the response code 200 and the json in its entirety. I've also excluded the possibility of it being the internet connectivity issue according to the following article. If I remove the screenshots field from the model there is no trouble parsing. Is it possible that the error persists due to GSON trying to deserialize the nonexistent fields 1, 2, and 3 and if so, how to deal with it?
Turn out the problem was in the JSON response formatting. If there were no screenshots it was formatted as a JSON array, otherwise it was formatted as a JSON object containing objects 1, 2, 3, etc. I've managed to fix it by following the answer on this stackoverflow question.
Using Retrofit here to consume Google Civic API.
The library requires you to create a model of what the API will return as I have done already with Election. Which is basically a copy of the google documentation.
(Retrofit binds the response properties to properties with the same name)
Election.Java :
public class Election {
private long id;
private String name;
private String electionDay;
private String ocdDivisionId;
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getElectionDay() {
return electionDay;
}
public void setElectionDay(String electionDay) {
this.electionDay = electionDay;
}
public String getOcdDivisionId() {
return ocdDivisionId;
}
public void setOcdDivisionId(String ocdDivisionId) {
this.ocdDivisionId = ocdDivisionId;
}
}
But Representatives have an inconsistent property name, thus I don't see a way to model this in a way Retrofit will know how to deserialize the API's response.
Representatives object (JSON) :
property name is called (key)
How do I let Retrofit deserialize a model that captures the property named variable after a key of the division?
Assuming you're using a Gson converter, I personally would use a map. I guess the same can be achieved with other converters, but I never used them. Say you have the following object:
public class Division {
#SerializedName("name")
#Expose
private String name;
#SerializedName("alsoKnownAs")
#Expose
private List<String> alsoKnownAs = new ArrayList<>();
#SerializedName("officeIndices")
#Expose
private List<Integer> officeIndices = new ArrayList<>();
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<String> getAlsoKnownAs() {
return alsoKnownAs;
}
public void setAlsoKnownAs(List<String> alsoKnownAs) {
this.alsoKnownAs = alsoKnownAs;
}
public List<Integer> getOfficeIndices() {
return officeIndices;
}
public void setOfficeIndices(List<Integer> officeIndices) {
this.officeIndices = officeIndices;
}
}
Which represents the object inside the divisions array. You can then have the class:
private class Divisions {
#SerializedName("divisions")
#Expose
private Map<String, Division> divisions = new HashMap<>();
// ...
}
Notice the usage of a map here? Behind the scenes Gson will be able to serialise and deserialise your objects. The class Divisions is the root of the json you gave us in the question.
Hope this helps
I'm being given a Json file with the form:
{
"descriptions": {
"desc1": "someString",
"desc2": {"name":"someName", "val": 7.0}
}
}
I have the POJO:
public class CustomClass {
Map<String, Object> descriptions;
public static class NameVal{
String name;
double val;
public NameVal(String name, double val){...}
}
}
I can recreate the json file with the code:
CustomClass a = new CustomClass();
a.descriptions = new HashMap<String, Object>();
a.descriptions.put("desc1", "someString");
a.descriptions.put("desc2", new CustomClass.NameVal("someName", 7.0));
new ObjectMapper().writeValue(new File("testfile"), a);
But, when I read the object back in using:
CustomClass fromFile = new ObjectMapper().readValue(new File("testfile"), CustomClass.class);
then fromFile.descriptions.get("desc2") is of type LinkedHashMap instead of type CustomClass.NameVal.
How can I get Jackson to properly parse the type of the CustomClass.NameVal descriptors (other than making some class that wraps the parsing and explicitly converts the LinkedHashMap after Jackson reads the file)?
Try this. Create a class Description with name and value attributes:
public class Description {
private String name;
private double val;
}
Now in your CustomClass do this:
public class CustomClass {
List<Description> descriptions;
}
And that's it. Remember to create getters and setters because Jackson needs it.
You could try something like this:
public class DescriptionWrapper {
private Description descriptions;
public Description getDescriptions() {
return descriptions;
}
public void setDescriptions(Description descriptions) {
this.descriptions = descriptions;
}
}
public class Description {
private String desc1;
private NameValue desc2;
public String getDesc1() {
return desc1;
}
public void setDesc1(String desc1) {
this.desc1 = desc1;
}
public NameValue getDesc2() {
return desc2;
}
public void setDesc2(NameValue desc2) {
this.desc2 = desc2;
}
}
public class NameValue {
private String name;
private double val;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getVal() {
return val;
}
public void setVal(double val) {
this.val = val;
}
}
I have an issue. I want to convert an object into another object using JAXB. As in, I have a class com.home.Student, and another class com.school.Student, both have same arguments, in fact both are same (copy paste), but different package. I want to perform the conversion between them using JAXB.
How to do that, please help me.
It would be nice if you included some code that explains your problem.
JAXB 101 says you should place the right annotations, then you can serialize and deserialize correctly. You should properly annotate your classes with #XmlRootElement, #XmlElement, #XmlAttribute, etc
For example:
#XmlRootElement(name="student")
#XmlAccessorType(XmlAccessType.NONE)
class Student {
#XmlElement(name="name")
private String name;
#XmlElement(name="age")
private int age;
public Student() {
}
public String getName() { return name; }
public int getAge() { return age; }
}
Then you can use serialize it using JAXB Marshaller:
StringWriter writer = new StringWriter();
JAXBContext context = JAXBContext.newInstance(Student.class);
Marshaller m = context.createMarshaller();
m.marshal(student, writer);
And deserialize it as well by Unmarshelling the input ..
JAXBContext context = JAXBContext.newInstance(Student.class);
Unmarshaller m = context.createUnmarshaller();
return (Student)m.unmarshal(new StringReader(input));
Make sure you look at the JavaDoc I mentioned above since there are many ways to do so.
If you cannot modify your classes, you can still use JAXB (or you can use XStream) Assuming your class is the following:
class Student {
private String name;
private int age;
public Student() {
}
public void setName(String name) { this.name = name; }
public String getName() { return name; }
public void setAge(int age) { this.age = age; }
public int getAge() { return age; }
}
You can serialize it by doing:
Student student = new Student();
student.setAge(25);
student.setName('FooBar');
StringWriter writer = new StringWriter();
JAXBContext context = JAXBContext.newInstance(Student.class);
Marshaller m = context.createMarshaller();
m.marshal(new JAXBElement(new QName(Student.class.getSimpleName()), Student.class, student), writer);
System.out.println(writer.toString());
If you are using XStream, you can do the serialization without Annotations too (and it is more controllable).
http://x-stream.github.io/tutorial.html
You could do the following.
Note:
It does not require that you ever materialize the data as XML, by leveraging JAXBSource.
It does not require any annotations on your object model.
com.home.Student
package com.home;
public class Student {
private String name;
private Status status;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Status getStatus() {
return status;
}
public void setStatus(Status status) {
this.status = status;
}
}
com.home.Status
package com.home;
public enum Status {
FULL_TIME("F"),
PART_TIME("P");
private final String code;
Status(String code) {
this.code = code;
}
public String getCode() {
return code;
}
}
com.school.Student
package com.school;
public class Student {
private String name;
private Status status;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Status getStatus() {
return status;
}
public void setStatus(Status status) {
this.status = status;
}
}
com.school.Status
package com.school;
public enum Status {
FULL_TIME("F"),
PART_TIME("P");
private final String code;
Status(String code) {
this.code = code;
}
public String getCode() {
return code;
}
}
com.example.Demo;
package com.example;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.Unmarshaller;
import javax.xml.bind.util.JAXBSource;
import javax.xml.namespace.QName;
public class Demo {
public static void main(String[] args) throws Exception {
com.home.Student studentA = new com.home.Student();
studentA.setName("Jane Doe");
studentA.setStatus(com.home.Status.FULL_TIME);
JAXBContext contextA = JAXBContext.newInstance(com.home.Student.class);
JAXBElement<com.home.Student> jaxbElementA = new JAXBElement(new QName("student"), com.home.Student.class, studentA);
JAXBSource sourceA = new JAXBSource(contextA, jaxbElementA);
JAXBContext contextB = JAXBContext.newInstance(com.school.Student.class);
Unmarshaller unmarshallerB = contextB.createUnmarshaller();
JAXBElement<com.school.Student> jaxbElementB = unmarshallerB.unmarshal(sourceA, com.school.Student.class);
com.school.Student studentB = jaxbElementB.getValue();
System.out.println(studentB.getName());
System.out.println(studentB.getStatus().getCode());
}
}
If your goal is simply to convert (assign actually) between the two, and they're identical except package name, I would think you could use simple reflection. Just iterate over the fields of source object, and assign to the field of the same name in the target object. Vaguely, like this:
import java.lang.reflect.Field;
public class Converter {
public void convert (com.home.Student src, com.school.Student dst) throws Exception {
for (Field f : src.getFields()) {
// src field name
String name = f.getName();
// get corresponding field in dst
Field dstField = dst.getDeclaredField(name);
dstField.set(dst, f.get());
}
}
}
Note: I didn't compile this, which is why I say "vaguely". You'll need to use the Field.isAccessible()/Field.setAccessible(true) if the fields are private, so that you can temporarily change accessibility while you're assigning values. Or, you an write slightly more complex code that uses public setters/getters rather than directly using field access.