Using #JsonIgnore selectively on parent class fields - json

I have a Class A which is extended by multiple classes, say Class B, Class C and Class D.
However I want only Class D to ignore super class fields during serialization.
How do I implement this? If I use #JsonIgnore annotation on parent Class A, all child classes get impacted.

I can see 2 ways:
1 - You can use a JacksonAnnotationIntrospector to dynamically ignore fields, here we test if the field is from class A (see example below when serialising class C)
class CustomIntrospector extends JacksonAnnotationIntrospector {
#Override
public boolean hasIgnoreMarker(final AnnotatedMember m) {
return m.getDeclaringClass() == A.class;
}
}
2 - You can use the #JsonIgnoreProperties annotation to ignore the fields you don't want (see example below on the definition of class D)
Then with the following class
class A {
public String fieldA = "a";
}
class B extends A {
public String fieldB = "b";
}
class C extends A {
public String fieldC = "c";
}
#JsonIgnoreProperties(value = { "fieldA" })
class D extends A {
public String fieldD = "d";
}
Then use the ObjectMapper
public static void main(String[] args) throws Exception {
A a = new A();
String jsonA = new ObjectMapper().writeValueAsString(a);
System.out.println(jsonA);
// No filtering, will output all fields
B b = new B();
String jsonB = new ObjectMapper().writeValueAsString(b);
System.out.println(jsonB);
// Using the CustomIntrospector to filter out fields from class A
C c = new C();
ObjectMapper mapper = new ObjectMapper().setAnnotationIntrospector(new CustomIntrospector());
String jsonC = mapper.writeValueAsString(c);
System.out.println(jsonC);
// Using #JsonIgnoreProperties to filter out fields from class A
D d = new D();
String jsonD = new ObjectMapper().writeValueAsString(d);
System.out.println(jsonD);
}
outputs
{"fieldA":"a"}
{"fieldA":"a","fieldB":"b"}
{"fieldC":"c"}
{"fieldD":"d"}

Related

Room returns incorrect initialized object from generated query

I have three tables, one containing Cards, one containing CardDecks and third one implementing a many-to-many relation between the former two and additionally containg a symbol for every relation entry.
My task is to get three columns from the card-table and the symbol from the relation-table and save it in a data Object specifically designed for handling those inputs, the codition being, that all entries match the given deckId. Or in (hopefully correct) sql-language:
#Query("SELECT R.symbol, C.title, C.type, C.source " +
"FROM card_table C JOIN cards_to_card_deck R ON C.id = R.card_id"+
"WHERE R.card_deck_id = :cardDeckId")
LiveData<List<CardWithSymbol>> getCardsWithSymbolInCardDeckById(long cardDeckId);
But the room implementation class generates:
#Override
public LiveData<List<CardWithSymbol>> getCardsWithSymbolInCardDeckById(long
cardDeckId) {
final String _sql = "SELECT R.symbol, C.title, C.typ, C.source FROM
cards_to_card_deck R INNER JOIN card_table C ON R.card_id = C.id WHERE
R.card_deck_id = ?";
final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, 1);
int _argIndex = 1;
_statement.bindLong(_argIndex, cardDeckId);
return new ComputableLiveData<List<CardWithSymbol>>() {
private Observer _observer;
#Override
protected List<CardWithSymbol> compute() {
if (_observer == null) {
_observer = new Observer("cards_to_card_deck","card_table") {
#Override
public void onInvalidated(#NonNull Set<String> tables) {
invalidate();
}
};
__db.getInvalidationTracker().addWeakObserver(_observer);
}
final Cursor _cursor = __db.query(_statement);
try {
final int _cursorIndexOfSymbol = _cursor.getColumnIndexOrThrow("symbol");
final List<CardWithSymbol> _result = new ArrayList<CardWithSymbol>(_cursor.getCount());
while(_cursor.moveToNext()) {
final CardWithSymbol _item;
final int _tmpSymbol;
_tmpSymbol = _cursor.getInt(_cursorIndexOfSymbol);
_item = new CardWithSymbol(_tmpSymbol,null,null,null);
_result.add(_item);
}
return _result;
} finally {
_cursor.close();
}
}
#Override
protected void finalize() {
_statement.release();
}
}.getLiveData();
}
Where
_item = new CardWithSymbol(_tmpSymbol,null,null,null);
should return my fully initialized object.
The CardWithSymbol class is declared as follows:
public class CardWithSymbol {
public int symbol;
public String cardName;
public String cardType;
public String cardSource;
public CardWithSymbol(int symbol, String cardName, String cardType, String cardSource){
this.symbol = symbol;
this.cardName = cardName;
this.cardType = cardType;
this.cardSource = cardSource;
}
And the types of the columns returned by the query are:
int symbol, String title, String type, String source
I already went through some debugging and the rest of the application works just fine. I can even read the symbol from the objects return by the query, but as mentioned above for some reason room ignores the other three parameters and just defaults them to null in the query-implementation.
So after some trial and error and reading through the dao-documentation once again i found my error:
When creating a class for handling subsets of columns in room, it is important to tell room which variable coresponds to which columns via #ColumnInfo(name = "name of the column goes here")-annotation.
So changing my CardWithSymbol class as follows solved the issue for me:
import android.arch.persistence.room.ColumnInfo;
public class CardWithSymbol {
#ColumnInfo(name = "symbol")
public int symbol;
#ColumnInfo(name = "title")
public String cardName;
#ColumnInfo(name = "type")
public String cardType;
#ColumnInfo(name = "source")
public String cardSource;
public CardWithSymbol(int symbol, String cardName, String cardType, String cardSource){
this.symbol = symbol;
this.cardName = cardName;
this.cardType = cardType;
this.cardSource = cardSource;
}
}

Jackson serialize Animal and Cat with 2 different serializers depending on the field type of a Cat instance (polymorphistic)

I have a class animal and a class cat that extends that:
class Animal {
protected String name;
...
}
class Cat extends Animal {
protected int livesLeft;
...
}
Each one has a separate JsonSerializer:
module.addSerializer(Cat.class, new CatSerializer());
module.addSerializer(Animal.class, new AnimalSerializer());
Now I want to serialize an instance of this class:
class Foo {
Cat catA = new Cat("Felix", 9);
Animal catB = new Cat("Madonna", 3);
}
But when I do that, both fields use the CatSerializer, so I get
{"catA" : {"name":"Felix", "livesLeft":9},
"catB" : {"name":"Madonna", "livesLeft":3}}
Which is something I can't deserialize, because the AnimalDeserializer needs to know the type of animal to be able to construct it.
Ideally it would use AnimalSerializer for the field Animal catB and I 'd get:
{"catA" : {"name":"Felix", "livesLeft":9},
"catB" : {"animalType":"Cat", "name":"Madonna", "livesLeft":3}}
which can be deserialized.
Workaround idea: Is there any way to determine the field type (not just the instance type) during serialization? So for field Animal catB = new Cat("Madonna", 3) that would return Animal, not Cat.
Since you say you don't want to annotate fields, you can define a ContextualSerializer that returns a serializer according to field type. You can then extend that for each Animal subtype instead of JsonSerializer. E.g. :
abstract class ByFieldTypeSerializer<T> extends JsonSerializer<T> implements ContextualSerializer {
#Override
public JsonSerializer<?> createContextual(SerializerProvider prov, BeanProperty property) throws JsonMappingException {
// getType will return the field type i.e. Animal for catB
// SerializerProvider argument knows about all serializers by type
return prov.findValueSerializer(property.getType());
}
}
class CatSerializer extends ByFieldTypeSerializer<Cat> {
#Override
public void serialize(Cat value, JsonGenerator gen, SerializerProvider serializers) throws IOException, JsonProcessingException {
// serialize a Cat
}
}
Then simply plug it in:
module.addSerializer(Animal.class, new AnimalSerializer());
// Delegates to AnimalSerializer if the field type is Animal instead of Cat
module.addSerializer(Cat.class, new CatSerializer());
I hope this solution works for you, or any appropriate tweak around this. What makes the behavior you are expecting is the usage of forType(Animal.class).
public static void main(String[] args) throws JsonProcessingException {
ObjectMapper objectMapper = new ObjectMapper();
Cat cat = new Cat();
cat.livesLeft=3;
cat.name="mycat";
System.out.println(objectMapper.writer().writeValueAsString(cat));
Animal animal = cat;
System.out.println(objectMapper.writer().forType(Animal.class).writeValueAsString(animal));
}
#Data
static abstract class Animal {
protected String name;
public String getAnimalType(){
return this.getClass().getSimpleName();
}
}
#JsonIgnoreProperties("animalType")
#Data
static class Cat extends Animal {
protected int livesLeft;
}
The output generated is as below:
{"name":"mycat","livesLeft":3}
{"name":"mycat","animalType":"Cat"}

Loading class attributes in Yii2

For example I have the following classes:
class A extends yii\base\Model {
public $attr1;
public $attr2;
}
class B extends yii\db\ActiveRecord {
... some attributes in table including attr1 and attr2 ...
}
Is it more elegant way to load() values from object B to object A than
$objectA = new A();
$objectB = new B();
... obtaining values for $objectB ...
$objectA->load([StringHelper::basename($objectB->className()) => $objectB->attributes])
?
You can do a
$objectA->attributes = $objectB->attributes;

How to prevent Gson serialize / deserialize the first character of a field (underscore)?

My class:
class ExampleBean {
private String _firstField;
private String _secondField;
// respective getters and setters
}
I want to appear as follows:
{
"FirstField":"value",
"SecondField":"value"
}
And not like this
{
"_FirstField":"value",
"_SecondField":"value"
}
I initialize the parser as follows:
GsonBuilder builder = new GsonBuilder();
builder.setDateFormat(DateFormat.LONG);
builder.setFieldNamingPolicy(FieldNamingPolicy.UPPER_CAMEL_CASE);
builder.setPrettyPrinting();
set_defaultParser(builder.create());
I could see the API and in the documentation of "FieldNamePolicy" but I am surprised that not give the option to skip "_"
I also know I can use the annotation...
# SerializedName (" custom_naming ")
...but do not want to have to write this for alllllll my fields ...
It's very useful for me to distinguish between local variables and fields of a class. :( Any Idea?
EDIT: There would be many obvious solutions, (inheritance, gson overwriting methods, regular expresions). My question is more focused on whether there is a native solution of gson or a less intrusive fix?
Maybe we could propose as new FieldNamePolicy?
GsonBuilder provides a method setFieldNamingStrategy() that allows you to pass your own FieldNamingStrategy implementation.
Note that this replaces the call to setFieldNamingPolicy() - if you look at the source for GsonBuilder these two methods are mutually exclusive as they set the same internal field (The FieldNamingPolicy enum is a FieldNamingStrategy).
public class App
{
public static void main(String[] args)
{
Gson gson = new GsonBuilder()
.setFieldNamingStrategy(new MyFieldNamingStrategy())
.setPrettyPrinting()
.create();
System.out.println(gson.toJson(new ExampleBean()));
}
}
class ExampleBean
{
private String _firstField = "first field value";
private String _secondField = "second field value";
// respective getters and setters
}
class MyFieldNamingStrategy implements FieldNamingStrategy
{
public String translateName(Field field)
{
String fieldName =
FieldNamingPolicy.UPPER_CAMEL_CASE.translateName(field);
if (fieldName.startsWith("_"))
{
fieldName = fieldName.substring(1);
}
return fieldName;
}
}
Output:
{
"FirstField": "first field value",
"SecondField": "second field value"
}
What you want is
import java.lang.reflect.Field;
import java.text.DateFormat;
import com.google.gson.FieldNamingStrategy;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
public class GsonExample {
public static void main(String... args) throws Exception {
final GsonBuilder builder = new GsonBuilder();
builder.setDateFormat(DateFormat.LONG);
builder.setPrettyPrinting();
builder.setFieldNamingStrategy(new FieldNamingStrategy() {
#Override
public String translateName(Field f) {
String fieldName = f.getName();
if(fieldName.startsWith("_") && fieldName.length() > 1) {
fieldName = fieldName.substring(1, 2).toUpperCase() + fieldName.substring(2);
}
return fieldName;
}
});
final Gson gson = builder.create();
System.out.println(gson.toJson(new ExampleBean("example", "bean")));
}
private static class ExampleBean {
private final String _firstField;
private final String _secondField;
private ExampleBean(String _firstField, String _secondField) {
this._firstField = _firstField;
this._secondField = _secondField;
}
}
}
which generates
{"FirstField":"example","SecondField":"bean"}

Global property filter in Jackson

Is there a way to register a global property filter in ObjectMapper?
Global means that it will be applied to all serialized beans. I can't use annotations (I can't modify serialized beans) and don't know what properties the beans have.
The filtering should be name based.
My first idea was to write a custom serializer, but I don't know what should I pass to the constructor.
I'd make use of a FilterProvider. It's a little involved, but not too unwieldy.
import org.codehaus.jackson.annotate.JsonAutoDetect.Visibility;
import org.codehaus.jackson.annotate.JsonMethod;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.map.ObjectWriter;
import org.codehaus.jackson.map.annotate.JsonFilter;
import org.codehaus.jackson.map.ser.FilterProvider;
import org.codehaus.jackson.map.ser.impl.SimpleBeanPropertyFilter;
import org.codehaus.jackson.map.ser.impl.SimpleFilterProvider;
public class JacksonFoo
{
public static void main(String[] args) throws Exception
{
Bar bar = new Bar();
bar.id = "42";
bar.name = "James";
bar.color = "blue";
bar.foo = new Foo();
bar.foo.id = "7";
bar.foo.size = "big";
bar.foo.height = "tall";
ObjectMapper mapper = new ObjectMapper().setVisibility(JsonMethod.FIELD, Visibility.ANY);
System.out.println(mapper.writeValueAsString(bar));
// output:
// {"id":"42","name":"James","color":"blue","foo":{"id":"7","size":"big","height":"tall"}}
String[] ignorableFieldNames = { "id", "color" };
FilterProvider filters = new SimpleFilterProvider().addFilter("filter properties by name", SimpleBeanPropertyFilter.serializeAllExcept(ignorableFieldNames));
mapper = new ObjectMapper().setVisibility(JsonMethod.FIELD, Visibility.ANY);
mapper.getSerializationConfig().addMixInAnnotations(Object.class, PropertyFilterMixIn.class);
ObjectWriter writer = mapper.writer(filters);
System.out.println(writer.writeValueAsString(bar));
// output:
// {"name":"James","foo":{"size":"big","height":"tall"}}
}
}
#JsonFilter("filter properties by name")
class PropertyFilterMixIn
{
}
class Bar
{
String id;
String name;
String color;
Foo foo;
}
class Foo
{
String id;
String size;
String height;
}
For other approaches and more information, I recommend the following resources.
http://wiki.fasterxml.com/JacksonJsonViews
http://www.cowtowncoder.com/blog/archives/2011/02/entry_443.html
http://wiki.fasterxml.com/JacksonFeatureJsonFilter
http://www.cowtowncoder.com/blog/archives/2011/09/entry_461.html