I have a structure with circular references.
And for debug purposes, I want to dump it. Basically as any format, but I chose JSON.
Since it can be any class, I chose GSON which doesn't needs JAXB annotations.
But GSON hits the circular references and recurses until StackOverflowError.
How can I limit GSON to
ignore certain class members?
Both #XmlTransient and #JsonIgnore are not obeyed.
ignore certain object graph paths? E.g. I could instruct GSON not to serialize release.customFields.product.
go to the depth of at most 2 levels?
Related: Gson.toJson gives StackOverFlowError, how to get proper json in this case? (public static class)
Simply make the fields transient (as in private transient int field = 4;). GSON understands that.
Edit
No need for a built-in annotation; Gson lets you plug in your own strategies for excluding fields and classes. They cannot be based on a path or nesting level, but annotations and names are fine.
If I wanted to skip fields that are named "lastName" on class "my.model.Person", I could write an exclusion strategy like this:
class MyExclusionStrategy implements ExclusionStrategy {
public boolean shouldSkipField(FieldAttributes fa) {
String className = fa.getDeclaringClass().getName();
String fieldName = fa.getName();
return
className.equals("my.model.Person")
&& fieldName.equals("lastName");
}
#Override
public boolean shouldSkipClass(Class<?> type) {
// never skips any class
return false;
}
}
I could also make my own annotation:
#Retention(RetentionPolicy.RUNTIME)
public #interface GsonRepellent {
}
And rewrite the shouldSkipField method as:
public boolean shouldSkipField(FieldAttributes fa) {
return fa.getAnnotation(GsonRepellent.class) != null;
}
This would enable me to do things like:
public class Person {
#GsonRepellent
private String lastName = "Troscianko";
// ...
To use a custom ExclusionStrategy, build Gson object using the builder:
Gson g = new GsonBuilder()
.setExclusionStrategies(new MyOwnExclusionStrategy())
.create();
I know this question has a few years now, but I'd like to contribute with my solution.
Although #fdreger's answer is completely valid in case you want to exclude a field always, it doesn't work if you want to exclude it just in certain cases, avoiding this way the recursion.
The way I approached the problem is:
I write my own JsonSerializer. In it, I define a static variable to control de number of times an object of this same class is serialize and depending on the value, the object can be serialized or not.
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import java.io.IOException;
public class UserJsonSerializer extends JsonSerializer<User> {
private static final ThreadLocal<Integer> depth = new ThreadLocal<Integer>() {
#Override
protected Integer initialValue() {
return 0;
}
};
#Override
public void serialize(User user, JsonGenerator generator, SerializerProvider provider) throws IOException, JsonProcessingException {
// Here, we limit the number of instances to return. In this case, just 1.
depth.set(depth.get() + 1);
if(depth.get() >= 1) {
generator.writeNull();
} else {
generator.writeObject(user);
}
}
public static void clear() {
depth.remove();
}
}
Bind the UserJsonSerializer to the class you want to control
public class SomeClass implements Serializable {
#JsonSerialize(using = UserJsonSerializer.class)
private User user;
//...others fields...
}
Don't forget to call UserJsonSerializer#clear() method to reinitialize the counter everytime you're going to parse a new entity.
I hope this helps.
Related
I have the following POJO using Immutables+Jackson under the hood:
#JsonInclude(JsonInclude.Include.NON_NULL)
abstract class AbstractQueryRequest {
#JsonProperty("reqid")
public abstract String reqid();
#JsonProperty("rawquery")
public abstract String rawquery();
}
At some point I need to build another object based on the fields of the POJO, something along this line:
final HttpUrl.Builder urlBuilder = HttpUrl.parse(cfg.baseUrl()).newBuilder();
urlBuilder.addQueryParameter("reqid", request.reqid())
.addQueryParameter("rawquery", request.rawquery());
It's quite annoying to keep the POJO and this call aligned upon changes, I was wondering if it was possible to access programmatically each JsonProperty instead of typing the string manually.
Note that it is fine to write the getters by hand as I can easily refactor and I have the compiler double checking, but for strings I am worried for people down the line and I would like to "read" them from the POJO class somehow.
You can do it via reflection. You need to take method annotation values which annotated with JsonProperty. But I recommend you to use JsonProperty on fields, not methods.
Here is an example for your current requirement :
public class Main {
public static void main(String[] args) {
AbstractQueryRequest someType = new SomeType();
for(Method method : x.getClass().getSuperclass().getDeclaredMethods()) {
if (method.isAnnotationPresent(JsonProperty.class)) {
JsonProperty annotation = method.getAnnotation(JsonProperty.class);
System.out.println(annotation.value());
}
}
}
}
class SomeType extends AbstractQueryRequest {
#Override
public String reqid() {
return null;
}
#Override
public String rawquery() {
return null;
}
}
Output is :
rawquery
reqid
I'm serializing some existing objects with Jackson 2.22, leveragin the MixIn feature to decouple the real object from the Jackson annotations configuration.
Actually my mixin is an interface that declares the same methods of the target class and annotates them, here's an example.
Target class:
public class Product {
// ...
public String getName();
public String getDescription();
public String getPrice();
public String getFinalPrice();
public String getDiscount();
// ...
}
and the mixin:
public interface ProductApi {
#JsonProperty
public String getName();
#JsonProperty("price")
public String getFinalPrice();
}
My JSON should have some more informations, computed from several methods or fields of the target class.
Is this even possible in Jackson?
I tried turning the mixin in a class and adding a new method there, but that didn't work.
public class ProductApi {
#JsonProperty
public String getName();
#JsonProperty("price")
public String getFinalPrice();
#JsonProperty("images")
public List<String> getImages() { /* ... */ }
}
I guess this is because the mixin only provides annotations for the target class, but is the latter that is read for serialization.
Of course, if I change the object to be serialized with a new subclass that contains the new method I need, that works, but the objects come from our services layers, and this would mean I have to rewrite all those methods.
I'm using Jackson with Jersey, so don't want to change Jackson with another library.
Here's how I did it.
The solution is to specify a custom JsonSerializer implementation to the field getter.
First of all, I changed the mixin interface to a class that extends the entity (target) class, so that it can access the target class data.
public class ProductApi extends Product {
#JsonProperty
#Override
public String getName() {
return super.getName();
};
// ...
}
Next, I implemented the JsonSerializer that would create the derived property I want:
public static class ImagesSerializer extends JsonSerializer<String> {
#Override
public void serialize(String value, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonProcessingException {
Product p = (Product) jgen.getCurrentValue();
int num = p.getNumberOfImages();
List<String> imgs = new ArrayList<String>(num);
for(int i = 0; i < num; i++) {
String src = "/include/images/showImage.jsp?"+"id="+p.getId()+"&number="+i;
imgs.add(src);
}
provider.defaultSerializeValue(imgs, jgen);
}
}
This is a really simple implementation, more safety checks should be done.
What this does is, basically, retrieve the whole entity instance from the JSON generator, build up a custom object and then ask Jackson to serialize it.
I implemented it inside my ProductApi as a static class, but just for simplicity.
Finally, the serializer needs to be bound to the JsonProperty annotated field:
public class ProductApi extends Product {
#JsonProperty
#Override
public String getName() {
return super.getName();
};
// ...
#JsonSerialize(using=ImagesSerializer.class)
#JsonProperty("images")
#Override
public String getImage() { // in my entity this returns an image number, whereas in my JSON I want a list of URLs
return "";
}
// ...
}
As a side note, it seems that the returned value of the getImage() method is not used.
Why don't you just make some fields, which should be serialized and use Gson for it?
I am able to serialize and deserialize a class hierarchy where the abstract base class is annotated with
#JsonTypeInfo(
use = JsonTypeInfo.Id.MINIMAL_CLASS,
include = JsonTypeInfo.As.PROPERTY,
property = "#class")
but no #JsonSubTypes listing the subclasses, and the subclasses themselves are relatively unannotated, having only a #JsonCreator on the constructor. The ObjectMapper is vanilla, and I'm not using a mixin.
Jackson documentation on PolymorphicDeserialization and "type ids" suggests (strongly) I need the #JsonSubTypes annotation on the abstract base class, or use it on a mixin, or that I need to register the subtypes with the ObjectMapper. And there are plenty of SO questions and/or blog posts that agree. Yet it works. (This is Jackson 2.6.0.)
So ... am I the beneficiary of an as-yet-undocumented feature or am I relying on undocumented behavior (that may change) or is something else going on? (I'm asking because I really don't want it to be either of the latter two. But I gots to know.)
EDIT: Adding code - and one comment. The comment is: I should have mentioned that all the subclasses I'm deserializing are in the same package and same jar as the base abstract class.
Abstract base class:
package so;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
#JsonTypeInfo(
use = JsonTypeInfo.Id.MINIMAL_CLASS,
include = JsonTypeInfo.As.PROPERTY,
property = "#class")
public abstract class PolyBase
{
public PolyBase() { }
#Override
public abstract boolean equals(Object obj);
}
A subclass of it:
package so;
import org.apache.commons.lang3.builder.EqualsBuilder;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
public final class SubA extends PolyBase
{
private final int a;
#JsonCreator
public SubA(#JsonProperty("a") int a) { this.a = a; }
public int getA() { return a; }
#Override
public boolean equals(Object obj) {
if (null == obj) return false;
if (this == obj) return true;
if (this.getClass() != obj.getClass()) return false;
SubA rhs = (SubA) obj;
return new EqualsBuilder().append(this.a, rhs.a).isEquals();
}
}
Subclasses SubB and SubC are the same except that field a is declared String (not int) in SubB and boolean (not int) in SubC (and the method getA is modified accordingly).
Test class:
package so;
import java.io.IOException;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.testng.annotations.Test;
import static org.assertj.core.api.Assertions.*;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.ObjectMapper;
public class TestPoly
{
public static class TestClass
{
public PolyBase pb1, pb2, pb3;
#JsonCreator
public TestClass(#JsonProperty("pb1") PolyBase pb1,
#JsonProperty("pb2") PolyBase pb2,
#JsonProperty("pb3") PolyBase pb3)
{
this.pb1 = pb1;
this.pb2 = pb2;
this.pb3 = pb3;
}
#Override
public boolean equals(Object obj) {
if (null == obj) return false;
if (this == obj) return true;
if (this.getClass() != obj.getClass()) return false;
TestClass rhs = (TestClass) obj;
return new EqualsBuilder().append(pb1, rhs.pb1)
.append(pb2, rhs.pb2)
.append(pb3, rhs.pb3)
.isEquals();
}
}
#Test
public void jackson_should_or_should_not_deserialize_without_JsonSubTypes() {
// Arrange
PolyBase pb1 = new SubA(5), pb2 = new SubB("foobar"), pb3 = new SubC(true);
TestClass sut = new TestClass(pb1, pb2, pb3);
ObjectMapper mapper = new ObjectMapper();
// Act
String actual1 = null;
TestClass actual2 = null;
try {
actual1 = mapper.writeValueAsString(sut);
} catch (IOException e) {
fail("didn't serialize", e);
}
try {
actual2 = mapper.readValue(actual1, TestClass.class);
} catch (IOException e) {
fail("didn't deserialize", e);
}
// Assert
assertThat(actual2).isEqualTo(sut);
}
}
This test passes and if you break at the second try { line you can inspect actual1 and see:
{"pb1":{"#class":".SubA","a":5},
"pb2":{"#class":".SubB","a":"foobar"},
"pb3":{"#class":".SubC","a":true}}
So the three subclasses got properly serialized (each with their class name as id) and then deserialized, and the result compared equal (each subclass has a "value type" equals()).
There are two ways to achieve polymorphism in serialization and deserialization with Jackson. They are defined in Section 1. Usage in the link you posted.
Your code
#JsonTypeInfo(
use = JsonTypeInfo.Id.MINIMAL_CLASS,
include = JsonTypeInfo.As.PROPERTY,
property = "#class")
is an example of the second approach. The first thing to note is that
All instances of annotated type and its subtypes use these settings
(unless overridden by another annotation)
So this config value propagates to all subtypes. Then, we need a type identifier that will map a Java type to a text value in the JSON string and vice versa. In your example, this is given by JsonTypeInfo.Id#MINIMAL_CLASS
Means that Java class name with minimal path is used as the type identifier.
So a minimal class name is generated from the target instance and written to the JSON content when serializing. Or a minimal class name is used to determine the target type for deserializing.
You could have also used JsonTypeInfo.Id#NAME which
Means that logical type name is used as type information; name will
then need to be separately resolved to actual concrete type (Class).
To provide such a logical type name, you use #JsonSubTypes
Annotation used with JsonTypeInfo to indicate sub types of
serializable polymorphic types, and to associate logical names used
within JSON content (which is more portable than using physical Java
class names).
This is just another way to achieve the same result. The documentation you're asking about states
Type ids that are based on Java class name are fairly
straight-forward: it's just class name, possibly some simple prefix
removal (for "minimal" variant). But type name is different: one has
to have mapping between logical name and actual class.
So the various JsonTypeInfo.Id values that deal with class names are straight-forward because they can be auto-generated. For type names, however, you need to give the mapping value explicitly.
I want to serialize a POJO class which is not under my control, but want to avoid serializing any of the properties which are coming from the superclass, and not from the final class. Example:
public class MyGeneratedRecord extends org.jooq.impl.UpdatableRecordImpl<...>,
example.generated.tables.interfaces.IMyGenerated {
public void setField1(...);
public Integer getField1();
public void setField2(...);
public Integer getField2();
...
}
You can guess from the example that that this class is generated by JOOQ, and inherits from a complex base class UpdatableRecordImpl which also has some bean property-like methods, which cause problems during the serialization. Also, I have several similar classes, so it would be good to avoid duplicating the same solution for all of my generated POJOs.
I have found the following possible solutions so far:
ignore the specific fields coming from superclass using mixin technique like this: How can I tell jackson to ignore a property for which I don't have control over the source code?
The problem with this is that if the base class changes (e.g., a new getAnything() method appears in it), it can break my implementation.
implement a custom serializer and handle the issue there. This seems a bit overkill to me.
as incidentally I have an interface which describes exactly the properties I want to serialize, maybe I can mixin a #JsonSerialize(as=IMyGenerated.class) annotation...? Can I use this for my purpose?
But, from pure design point of view, the best would be to be able to tell jackson that I want to serialize only the final class' properties, and ignore all the inherited ones. Is there a way to do that?
Thanks in advance.
You can register a custom Jackson annotation intropector which would ignore all the properties that come from the certain super type. Here is an example:
public class JacksonIgnoreInherited {
public static class Base {
public final String field1;
public Base(final String field1) {
this.field1 = field1;
}
}
public static class Bean extends Base {
public final String field2;
public Bean(final String field1, final String field2) {
super(field1);
this.field2 = field2;
}
}
private static class IgnoreInheritedIntrospector extends JacksonAnnotationIntrospector {
#Override
public boolean hasIgnoreMarker(final AnnotatedMember m) {
return m.getDeclaringClass() == Base.class || super.hasIgnoreMarker(m);
}
}
public static void main(String[] args) throws JsonProcessingException {
final ObjectMapper mapper = new ObjectMapper();
mapper.setAnnotationIntrospector(new IgnoreInheritedIntrospector());
final Bean bean = new Bean("a", "b");
System.out.println(mapper
.writerWithDefaultPrettyPrinter()
.writeValueAsString(bean));
}
}
Output:
{
"field2" : "b"
}
You can override the superclass' methods which you'd like to prevent from being output and annotate them with #JsonIgnore. The override shifts the control of property creation to the subclass while enabling its ability to filter it from the output.
For instance:
public class SomeClass {
public void setField1(...);
public Integer getField1();
public void setField2(...);
public Integer getField2();
#Override
#JsonIgnore
public String superClassField1(...){
return super.superClassField1();
};
#Override
#JsonIgnore
public String superClassField2(...){
return super.superClassField2();
};
...
}
You can use this as well instead of unnecessary overrides
#JsonIgnoreProperties({ "aFieldFromSuperClass"})
public class Child extends Base {
private String id;
private String name;
private String category;
}
The good use of inheritance is that the child classes extend or add functionality. So the usual way is to serialize the data.
A workarround would be to use a Value Object (VO) or Data Transfer Object (DTO) with the fields you need to serialize. Steps:
Create a VO class with the fields that should be serialized.
Use BeanUtils.copyProperties(target VO, source data) to copy the properties
Serialize the VO instance.
Add the following annotation in your Base Class :
#JsonInclude(Include.NON_NULL)
I want to be able to modify a java object from a JSON string without having to specify every field. Example:
package controller.test;
import com.google.gson.Gson;
public class Tests {
public Tests() {
test();
}
private void test() {
Person realPerson = new Person();
realPerson.setName("Bobby");
realPerson.setAge(28);
// Now trying to set the name from a JSON string
Gson gson = new Gson();
Person modifiedPerson = gson.fromJson("{\"name\":\"Justin\"}", Person.class);
System.out.println(gson.toJson(realPerson));
// prints: {"name":"Bobby","age":28}
System.out.println(gson.toJson(modifiedPerson));
// prints: {"name":"Justin","age":0}
}
public static void main(String[] args) {
new Tests();
}
}
class Person {
private String name;
private int age;
public Person() {
}
// Getters & setters are added here
}
How can I modify realPerson.name from "Bobby" to "Justin" without having to specify the age?
For a small class like this example it's not really a big problem: I could just check and set each field individually. But my class contains 138 (!) different fields.
EDIT:
My question was maybe poorly explained, let me sum up my problem:
I have an instance of a class (with ~130 different fields that are all set to individual values).
I want to be able to modify values in this instance using a JSON string.
Example JSON string: {"field13": "5", "field100": "2"}
Is there any way to accomplish this without having to write specific code for each field in my java class?
If it is an option to use Jackson as deserializer you could use the Annotations in the dataholder:
#JsonAnySetter
public void set(String name, Object value);
#JsonAnyGetter
public Map<String, Object> get();