I'm mapping object (code to which I have no access) into json using Jackson Object Mapper and MixIn class. So, for example i have this object:
public class Person {
private String name;
private String surname;
public Person (String name, String surname) {
this.name = name;
this.surname = surname;
}
public String getName() {
return name;
}
public String getSurname() {
return surname;
}
}
and MixIn class like this:
abstract class MixIn {
#JsonIgnore
abstract String getSurname(); // we don't need this in json output!
}
This working well. But what if i need to change output of "getName()" getter to return like return person.name + " " + person.surname.
Is this even possible to do by specific Object Mapper configuration? (when i have no access to Person class code)
You could write a custom serializer:
class PersonSerializer extends JsonSerializer<Person> {
#Override
public void serialize(Person person, JsonGenerator generator, SerializerProvider provider) throws IOException {
generator.writeStartObject();
generator.writeStringField("name", person.getName() + " " + person.getSurname());
generator.writeEndObject();
}
}
Then, there are multiple ways to register it, for example using a module:
ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
module.addSerializer(Person.class, new PersonSerializer());
mapper.registerModule(module);
System.out.println(mapper.writeValueAsString(new Person("user", "1827257")));
You may want to serialize the object differently in different cases, so using mixins is preferred:
objectMapper.getSerializationConfig().addMixInAnnotations(Person.class, MixIn.class);
String json = objectMapper.writeValueAsString(new Person("user", "1827257"));
Related
I have access to a RESTful API which returns JSON Strings, such as the following:
{
"Container1": {
"active": true
},
"Container2": {
"active": false
},
}
The problem is that the RESTful API is a bit maldesigned. The field name contains the data already. With the Jackson library it is not possible to deserialize the field name to a property name of the corresponding Java bean class. I assume, this isn't intended by the JSON specification neither. The above JSON string needs to be deserialized to an instance of the following class:
public class Container {
private Boolean active;
private String name;
}
I end up with UnrecognizedPropertyException for the field Container1.
I thought to configure to ignore unknown properties and to provide a JsonDeserializer for that property like this:
#JsonIgnoreProperties(ignoreUnknown = true)
public class Container {
private Boolean active;
private String name;
#JsonDeserialize(using = FieldNameToPropertyDeserializer.class)
public void setName(String name) {
this.name = name;
}
}
and the FieldNameToPropertyDeserializer:
public class FieldNameToPropertyDeserializer extends StdDeserializer<String> {
public FieldNameToPropertyDeserializer() {
super(String.class);
}
#Override
public String deserialize(JsonParser parser, DeserializationContext context) throws IOException, JsonProcessingException {
return parser.getCurrentName();
}
}
The invocation of the deserialization is achieved as follows:
String jsonString = response.readEntity(String.class);
ObjectMapper objectMapper = new ObjectMapper();
ObjectReader readerFor = objectMapper.readerFor(Container.class);
MappingIterator<Container> mappingIterator = readerFor.readValues(jsonString);
while (mappingIterator.hasNext()) {
Container container = (Container) mappingIterator.next();
containers.add(container);
}
But I only receive empty objects (properties set to null) because the parsing of the properties is skipped since I set #JsonIgnoreProperties(ignoreUnknown = true).
Is this possible at all? Or should I implement something like a post-processing afterwards?
How about this. Create a class ContainerActive like this
public class ContainerActive {
private boolean active;
// constructors, setters, getters
}
And you could just do
Map<String, ContainerActive> map = mapper.readValue(jsonString, new TypeReference<Map<String, ContainerActive>>() {});
With this you will have "Container1", "Container2" as the keys and ContainerActive Object as values which has active field.
Just a quick solution, if the object is such that, that all of it object is a container object you can receive the JSON inside and JSONObject you may use below code
import java.io.IOException;
import org.json.JSONException;
import org.json.JSONObject;
import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
public class TestSO {
public static void main(String[] args) throws JsonParseException, JsonMappingException, JSONException, IOException {
String jsonString = "{\r\n" +
" \"Container1\": {\r\n" +
" \"active\": true\r\n" +
" },\r\n" +
" \"Container2\": {\r\n" +
" \"active\": false\r\n" +
" },\r\n" +
"}";
JSONObject jsonObject = new JSONObject(jsonString);
ObjectMapper mapper = new ObjectMapper();
for (String key : jsonObject.keySet()) {
Container container = mapper.readValue(jsonObject.get(key).toString(), Container.class);
System.out.println(container);
}
}
static class Container{
private String name;
private Boolean active;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Boolean getActive() {
return active;
}
public void setActive(Boolean active) {
this.active = active;
}
#Override
public String toString() {
return "Container [name=" + name + ", active=" + active + "]";
}
}
}
It is possible to deserialize to a class with private fields and a custom argument constructor without using annotations and without modifying the class, using Jackson?
I know it's possible in Jackson when using this combination: 1) Java 8, 2) compile with "-parameters" option, and 3) the parameters names match JSON. But it's also possible in GSON by default without all these restrictions.
For example:
public class Person {
private final String firstName;
private final String lastName;
private final int age;
public Person(String firstName, String lastName, int age) {
this.firstName = firstName;
this.lastName = lastName;
this.age = age;
}
public static void main(String[] args) throws IOException {
String json = "{firstName: \"Foo\", lastName: \"Bar\", age: 30}";
System.out.println("GSON: " + deserializeGson(json)); // works fine
System.out.println("Jackson: " + deserializeJackson(json)); // error
}
public static Person deserializeJackson(String json) throws IOException {
ObjectMapper mapper = new ObjectMapper();
mapper.enable(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES);
mapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY);
return mapper.readValue(json, Person.class);
}
public static Person deserializeGson(String json) {
Gson gson = new GsonBuilder().create();
return gson.fromJson(json, Person.class);
}
}
Which works fine for GSON, but Jackson throws:
Exception in thread "main" com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `jacksonParametersTest.Person` (no Creators, like default construct, exist): cannot deserialize from Object value (no delegate- or property-based Creator)
at [Source: (String)"{firstName: "Foo", lastName: "Bar", age: 30}"; line: 1, column: 2]
at com.fasterxml.jackson.databind.exc.InvalidDefinitionException.from(InvalidDefinitionException.java:67)
It's possible in GSON, so I would expect that there must be some way in Jackson without modifying the Person class, without Java 8, and without an explicit custom deserializer. Does anybody know a solution?
Update, additional info
Gson seems to skip the argument constructor, so it must be creating a no-argument constructor behind the scenes using reflections.
Also, there exists a Kotlin Jackson module which is able to do this for Kotlin data classes, even without the "-parameters" compiler flag.
So it is strange that such a solution doesn't seem to exist for Java Jackson.
This is the (nice and clean) solution available in Kotlin Jackson (which IMO should also become available in Java Jackson via a custom module):
val mapper = ObjectMapper()
.enable(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES)
.registerModule(KotlinModule())
val person: Person = mapper.readValue(json, Person::class.java)
Solution with mix-in annotations
You could use mix-in annotations. It's a great alternative when modifying the classes is not an option. You can think of it as kind of aspect-oriented way of adding more annotations during runtime, to augment the statically defined ones.
Assuming that your Person class is defined as follows:
public class Person {
private final String firstName;
private final String lastName;
private final int age;
public Person(String firstName, String lastName, int age) {
this.firstName = firstName;
this.lastName = lastName;
this.age = age;
}
// Getters omitted
}
First define a mix-in annotation abstract class:
public abstract class PersonMixIn {
PersonMixIn(#JsonProperty("firstName") String firstName,
#JsonProperty("lastName") String lastName,
#JsonProperty("age") int age) {
}
}
Then configure ObjectMapper to use the defined class as a mix-in for your POJO:
ObjectMapper mapper = new ObjectMapper();
mapper.enable(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES);
mapper.addMixIn(Person.class, PersonMixIn.class);
And deserialize the JSON:
String json = "{firstName: \"Foo\", lastName: \"Bar\", age: 30}";
Person person = mapper.readValue(json, Person.class);
Since there is no default constructor, jackson or gson want create instance by there own. you should tell to the API how to create such instance by providing
custom deserialize.
here an snippet code
public class PersonDeserializer extends StdDeserializer<Person> {
public PersonDeserializer() {
super(Person.class);
}
#Override
public Person deserialize(JsonParser jp, DeserializationContext ctxt)
throws IOException, JsonProcessingException {
try {
final JsonNode node = jp.getCodec().readTree(jp);
final ObjectMapper mapper = new ObjectMapper();
final Person person = (Person) mapper.readValue(node.toString(),
Person.class);
return person;
} catch (final Exception e) {
throw new IOException(e);
}
}
}
Then register simple module as to handle your type
final ObjectMapper mapper = jacksonBuilder().build();
SimpleModule module = new SimpleModule();
module.addDeserializer(Person.class, new PersonDeserializer());
Jackson provides the module jackson-modules-java8 for solve your problem.
You must built your ObjectMapper as following:
ObjectMapper mapper = new ObjectMapper()
.enable(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES)
.registerModule(new ParameterNamesModule(JsonCreator.Mode.PROPERTIES));
You must add -parameters as compiler argument.
Example for maven:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.5.1</version>
<configuration>
<!--somecode-->
<compilerArgument>-parameters</compilerArgument>
</configuration>
</plugin>
For gradle:
compileJava {
options.compilerArgs << "-parameters"
}
Only if you can change your class implementation, below solution works
A simple solution is just to create a default constructor
Person() in Person class
public class Person {
private final String firstName;
private final String lastName;
private final int age;
public Person() {
}
public Person(String firstName, String lastName, int age) {
this.firstName = firstName;
this.lastName = lastName;
this.age = age;
}
...
}
I have a spring project and class like that and want to produce json with root name as type. Here is an example:
public class Person {
private String type; //worker
private String name; //Dennis
private String surname; //Ritchie
}
Result should be:
{"worker" : {
"name" : "Dennis" ,
"surname" : "Ritchie"
}
}
Can I do it with Json tags like #JsonRootName or should I write a Class for worker and extend Person class (There are 3 different types)?
You can implement a custom Serializer when you need to serialize a object into a JSON with a different form:
public class PersonSerializer extends JsonSerializer<Person> {
#Override
public void serialize(Person person, JsonGenerator jgen, SerializerProvider provider)
throws IOException, JsonProcessingException {
jgen.writeStartObject();
jgen.writeFieldName(person.getType());
jgen.writeStartObject();
jgen.writeFieldName("name", person.getName());
jgen.writeFieldName("surname", person.getSurname());
jgen.writeEndObject();
jgen.writeEndObject();
}
}
After that, you can register the serializer on the class:
#JsonSerialize(using = PersonSerializer.class)
public class Person {
private String type;
private String name;
private String surname;
}
We have a structure that represents configuration of some sort. We have had a typo in the word periodicity, it was wrongly spelled with 'o' as period*o*city. Below example source is the corrected one. However, I need to be able to read the old configuration files to maintain backwards compatibility.
Can I make JSON Jackson recognize the misspelled field/property on deserialization but ignore it on serialization?
We are using version 2.6.6 of JSON Jackson.
package foo;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
#JsonInclude(Include.NON_EMPTY)
#JsonIgnoreProperties(ignoreUnknown = true)
public class Rule {
private LogPeriodicity periodicityLevel;
private Integer periodicity;
// ctors and some other methods omitted for brevity
public LogPeriodicity getPeriodicityLevel() {
return periodicityLevel;
}
public void setPeriodicityLevel(LogPeriodicity periodicityLevel) {
this.periodicityLevel = periodicityLevel;
}
public Integer getPeriodicity() {
return periodicity;
}
public void setPeriodicity(Integer periodicity) {
this.periodicity = periodicity;
}
}
If i got your question right you want something like this?
MyClass obj = mapper.readValue("{ \"name\" : \"value\"}", MyClass.class);
String serialized = mapper.writeValueAsString(obj);
MyClass obj2 = mapper.readValue("{ \"name2\" : \"value\"}", MyClass.class);
String serialized2 = mapper.writeValueAsString(obj2);
if( Objects.equals(serialized2, serialized))
System.out.println("Success " + serialized + " == " + serialized2 );
if you don't want extra field in POJO you can just add setter like this:
public static class MyClass {
#JsonProperty
private String name = null;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
#JsonSetter
public void setName2(String name2) {
setName(name2);
}
}
You can probably also register legacy Mixin instead of #JsonSetter
public abstract class LegacyMyClassMixIn{
#JsonProperty("name")
private String name;
#JsonGetter("name")
public abstract String getName();
#JsonSetter("name2")
public abstract void setName(String name) ;
}
And use it like this:
SimpleModule module = new SimpleModule();
module.setMixInAnnotation(MyClass.class, LegacyMyClassMixIn.class);
mapper2.registerModule(module);
Btw in Gson it can be done with just 1 line #SerializedName(value="name", alternate={"name2"}) public String name = null;
I have two class
public class Person {
private long id;
private String name;
private Gender gender;
// getter and setter omitted
}
and
public class Gender {
private long id;
private String value;
// getter and setter omitted
}
By default the JSON mapping with Jackson library of a Person object is:
{
id: 11,
name: "myname",
gender: {
id: 2,
value: "F"
}
}
I'd like to known how to configure Jackson to obtain:
{
id: 11,
name: "myname",
gender: "F"
}
I don't want mapping all the Gender object but only its value property.
You can use a custom serializer:
public class GenderSerializer extends JsonSerializer<Gender> {
public GenderSerializer() {
}
#Override
public void serialize(Gender gender, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonProcessingException {
jgen.writeString(gender.getValue());
}
}
And in your Person class:
public class Person {
private long id;
private String name;
#JsonSerialize(using = GenderSerializer.class)
private Gender gender;
}
you might wanna see this for custom mapping OR if you need a short cut then you can change getter/setter of gender like this
public String getGender(String type){
this.getGender().getValue();
}
public void setGender(String value){
Gender gender = new Gender();
gender.setId(2);
gender.setValue(value);
this.gender = gender;
}
further you can also put condition for setting male/female on value= M/F
No need for custom serializer/deserializer if you can modify Gender class. For serialization you can use #JsonValue, for deserialization simple constructor (optionally annotated with #JsonCreator):
public class Gender {
#JsonCreator // optional
public Gender(String desc) {
// handle detection of "M" or "F"
}
#JsonValue
public String asString() { // name doesn't matter
if (...) return "M";
return "F";
}
}
Alternatively you could use a static factory method instead of constructor; if so, it must be annotated with #JsonCreator.
And return type of method annotated with #JsonValue can be anything that Jackson knows how to serialize; here we use String, but Enum, POJO and such are fine as well.