FasterXml: filter collection - java

Question How to indicate ObjectMapperthat he should filter object's nested collection by some criteria (field). See explanation via code:
Explanation via code:
I have to convert Container object to JSON. But I want to filter entries collection based on Entry.value field. I mean I want to serialize Container and include only that Entries which value == 1.
public class Container {
List<Entry> entries;
public void setEntries(List<Entry> entries) {
this.entries = entries;
}
}
public class Entry {
int value;
public Entry(int value) {
this.value = value;
}
}
public static void main(String[] args) {
ObjectMapper mapper = new ObjectMapper();
Container container = new Container();
container.setEntries(new LinkedList<Entry>({
{
add(new Entry(1));
add(new Entry(2));
add(new Entry(1));
}
}))
// Now I want to get container object only with two elements in list
mapper.writeValueAsString(container);
}

You can make Entry implement JsonSerializable. In Jackson 2.x it will give:
public class Entry
implements JsonSerializable
{
int value;
public Entry(int value)
{
this.value = value;
}
#Override
public void serialize(final JsonGenerator jgen,
final SerializerProvider provider)
throws IOException
{
// Don't do anything if value is not 1...
if (value != 1)
return;
jgen.writeStartObject();
jgen.writeNumberField("value", 1);
jgen.writeEndObject();
}
#Override
public void serializeWithType(final JsonGenerator jgen,
final SerializerProvider provider, final TypeSerializer typeSer)
throws IOException
{
serialize(jgen, provider);
}
}
Another solution would be to implement a custom JsonSerializer<Entry> and register it before you serialize; it would basically do the same as the above.

Related

Java Jackson serializer including FQCN

I'm trying to create a generic Jackon polymorphic serializer that is able to serialize and deserialize to and from JSON with this format including the fqcn of the class of the object:
{
"fqcn": "full qualified class name of the object",
"data": "serialized object"
}
This wrapper should be applied to any object, so for example this will be the JSON representation of a HashMap> object:
{
"fqcn": "java.util.HashMap",
"data": {
"key1": {
"fqcn": "java.util.ArrayList",
"data": [
{
"fqcn": "java.lang.String",
"data": "value1"
},
{
"fqcn": "java.lang.String",
"data": "value2"
}
]
},
"key2": {
...
}
}
}
I could use a MixIn annotation all objects with #JsonTypeInfo
#JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.WRAPPER_OBJECT)
public interface ObjMixin {
}
---
ObjectMapper mapper = new ObjectMapper();
mapper.addMixIn(Object.class, ObjMixin.class);
However, the format does not match with the required format: {"fqcn": ..., "data": ...}
I've also tried to register a StdConverter to convert any objects to a wrapper like this:
public class ObjectWrapper {
private String fqcn;
private Object data;
public ObjectWrapper(Object obj) {
this.fqcn = obj.getClass.getCanonicalName();
this.data = obj;
}
}
However it is not possible to create a StdDelegatingSerializer for Object.class.
With a custom StdSerializer like the following I am getting StackOverflowError:
#Override
public void serialize(Object obj, JsonGenerator jsonGen, SerializerProvider serializerProvider) throws IOException {
jsonGen.writeStartObject();
jsonGen.writeStringField("fqcn", obj.getClass().getCanonicalName());
jsonGen.writeFieldName("data");
if (obj instanceof Iterable) {
jsonGen.writeStartArray();
// Recursive serialization of all elements in the iterable
jsonGen.writeEndArray();
} else if (obj instanceof Map) {
jsonGen.writeStartObject();
// Recursive serialization of all elements in the map
jsonGen.writeEndObject();
} else {
// Infinite recursion here because I'm defining this serializer for Object.class
serializerProvider.defaultSerializeValue(obj, jsonGen);
}
}
Does anyone know any other solution to be able to achieve this?
You could use a custom serializer and custom serializer provider to wrap every object you want to serialize into this wrapper object (EDIT: that did not work recusrively, updated the code to not use the wrapper object but write the fields instead):
public class FQCNTest {
#Test
public void doTest() throws JsonProcessingException {
final ObjectMapper om = getObjectMapper();
final Object obj = getTestObject();
final String json = om.writeValueAsString(obj);
System.out.println(json); // {"fqcn":"java.util.HashMap","data":{"k":{"fqcn":"java.lang.String","data":"v"}}}
final Object obj2 = getTestValue();
final String json2 = om.writeValueAsString(obj2);
System.out.println(json2); // {"fcqn":"java.lang.String","data":"hello"}
final Object obj3 = null;
final String json3 = om.writeValueAsString(obj3);
System.out.println(json3); // null
}
private ObjectMapper getObjectMapper() {
final ObjectMapper mapper = new ObjectMapper();
final SerializerProvider sp = mapper.getSerializerProviderInstance();
mapper.setSerializerProvider(new CustomSerializerProvider(sp, mapper.getSerializerFactory()));
return mapper;
}
private Object getTestObject() {
final HashMap<Object, Object> hashMap = new HashMap<>();
hashMap.put("k", "v");
return hashMap;
}
private Object getTestValue() {
return "hello";
}
}
class CustomSerializerProvider extends DefaultSerializerProvider {
private final SerializerProvider defaultInstance;
protected CustomSerializerProvider(final SerializerProvider defaultInstance, final SerializerFactory f) {
super(defaultInstance, defaultInstance.getConfig(), f);
this.defaultInstance = defaultInstance;
}
#Override
public WritableObjectId findObjectId(final Object forPojo, final ObjectIdGenerator<?> generatorType) {
return defaultInstance.findObjectId(forPojo, generatorType);
}
#Override
public JsonSerializer<Object> serializerInstance(final Annotated annotated, final Object serDef) throws JsonMappingException {
return new CustomSerializer();
}
#Override
public Object includeFilterInstance(final BeanPropertyDefinition forProperty, final Class<?> filterClass) {
try {
return defaultInstance.includeFilterInstance(forProperty, filterClass);
} catch (final JsonMappingException e) {
throw new RuntimeException(e);
}
}
#Override
public boolean includeFilterSuppressNulls(final Object filter) throws JsonMappingException {
return defaultInstance.includeFilterSuppressNulls(filter);
}
#Override
public DefaultSerializerProvider createInstance(final SerializationConfig config, final SerializerFactory jsf) {
return this;
}
#Override
public void serializeValue(final JsonGenerator gen, final Object value) throws IOException {
new CustomSerializer().serialize(value, gen, this);
}
#Override
public void serializeValue(final JsonGenerator gen, final Object value, final JavaType rootType) throws IOException {
super.serializeValue(gen, value, rootType);
}
#Override
public void serializeValue(final JsonGenerator gen, final Object value, final JavaType rootType, final JsonSerializer<Object> ser) throws IOException {
super.serializeValue(gen, value, rootType, ser);
}
}
class CustomSerializer extends StdSerializer<Object> {
protected CustomSerializer() {
super(Object.class);
}
#Override
public void serialize(final Object value, final JsonGenerator gen, final SerializerProvider provider) throws IOException {
if (value == null) {
provider.defaultSerializeValue(value, gen);
return;
}
final Class<?> clazz = value.getClass();
final JsonSerializer<Object> serForClazz = provider.findValueSerializer(clazz);
gen.writeStartObject();
gen.writeStringField("fqcn", clazz.getCanonicalName());
gen.writeFieldName("data");
if (value instanceof Iterable) {
gen.writeStartArray();
for (final Object e : ((Iterable<?>) value)) {
final JsonSerializer<Object> ser = new CustomSerializer();
ser.serialize(e, gen, provider);
}
gen.writeEndArray();
} else if (value instanceof Map) {
gen.writeStartObject();
// Recursive serialization of all elements in the map
for (final Map.Entry<?, ?> e : ((Map<?, ?>) value).entrySet()) {
final String key = e.getKey().toString(); // need to handle keys better
final Object mapValue = e.getValue();
gen.writeFieldName(key);
final JsonSerializer<Object> ser = new CustomSerializer();
ser.serialize(mapValue, gen, provider);
}
gen.writeEndObject();
} else {
serForClazz.serialize(value, gen, provider);
}
gen.writeEndObject();
}
}
Note: this code may contain too much stuff that is not necessary, I just took it far enough to make it work for the specific example. (and did not test deserialization, that may be a totally different thing)

Jackson Serialization: Unwrap collection elements using

Is there a way to serialize collection and its elements unwrapped?
For example I want to serialize unwrapped all components:
class Model {
#JsonProperty
#JsonUnwrapped
Collection<Object> components;
Model(Collection<Object> components) {
this.components = components;
}
static class Component1 {
#JsonProperty
String stringValue;
Component1(String stringValue) {
this.stringValue= stringValue;
}
}
static class Component2 {
#JsonProperty
int intValue;
Component2(int intValue) {
this.intValue= intValue;
}
}
public static void main(String[] args) throws JsonProcessingException {
Model model = new Model(Arrays.asList(new Component1("something"), new Component2(42)));
String json = new ObjectMapper().writeValueAsString(model);
System.out.println(json);
}
}
Expected:
{"stringValue":"something","intValue":42}
But actual result is:
{"components":[{"stringValue":"something"},{"intValue":42}]}
Custom serializer might help:
class ModelSerializer extends JsonSerializer<Model> {
#Override
public void serialize(Model model, JsonGenerator generator, SerializerProvider serializers) throws IOException {
generator.writeStartObject();
JsonSerializer<Object> componentSerializer = serializers.findValueSerializer(getClass());
JsonSerializer<Object> unwrappingSerializer = componentSerializer.unwrappingSerializer(NameTransformer.NOP);
unwrappingSerializer.serialize(this, generator, serializers);
generator.writeEndObject();
}
}
I can't see a way to do that without custom serialization. I recommend these 2 serializers:
class ValueSerializer extends JsonSerializer<Object> {
#Override
public void serialize(Object value, JsonGenerator gen, SerializerProvider sers) throws IOException {
for (Field field : value.getClass().getDeclaredFields()) {
try {
field.setAccessible(true);
gen.writeObjectField(field.getName(), field.get(value));
} catch (IllegalAccessException ignored) {
}
}
}
}
class ModelSerializer extends JsonSerializer<Model> {
#Override
public void serialize(Model model, JsonGenerator gen, SerializerProvider sers) throws IOException {
gen.writeStartObject();
for (Object obj : model.getComponents()) {
gen.writeObject(obj);
}
gen.writeEndObject();
}
}
Notice how we don't call writeStartObject() at ValueSerializer so no extra curly braces from here, neither from writeObjectField. On the other hand in ModelSerializer writheStartObject adds curly braces, and then we dump within them each object in components
You'd also need to annotate serializable classes to use these serializers e.g.
#JsonSerialize(using = ValueSerializer.class)
class Component1 {
#JsonSerialize(using = ValueSerializer.class)
class Component2 {
#JsonSerialize(using = ModelSerializer.class)
class Model {
Not elegant, but work code.
Sure about unique naming of key values
#JsonProperty
#JsonSerialize(using = CollectionSerializer.class)
Collection<Object> components;
static class CollectionSerializer extends JsonSerializer<Object> {
#Override
public void serialize(Object o, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException, JsonProcessingException {
jsonGenerator.writeStartObject();
if (o instanceof Collection) {
Collection c = (Collection) o;
for (Object el : c) {
if (el instanceof Component1) {
jsonGenerator.writeStringField("stringValue", ((Component1) el).stringValue);
}
if (el instanceof Component2) {
jsonGenerator.writeNumberField("intValue", ((Component2) el).intValue);
}
}
}
jsonGenerator.writeEndObject();
}
}

Jackson custom annotation for custom NULL value serialization

According to this answer:
https://stackoverflow.com/a/43342675/5810648
I wrote such serializer:
public class CustomSerializer extends StdSerializer<Double> implements ContextualSerializer {
private final NAifNull annotation;
public CustomSerializer() {
super(Double.class);
this.annotation = null;
}
public CustomSerializer(NAifNull annotation) {
super(Double.class);
this.annotation = annotation;
}
#Override
public void serialize(Double value, JsonGenerator gen, SerializerProvider provider) throws IOException {
if (annotation != null && value == null) {
gen.writeString("N/A");
} else {
gen.writeNumber(value);
}
}
#Override
public JsonSerializer<?> createContextual(SerializerProvider prov, BeanProperty property) {
NAifNull annotation = property.getAnnotation(NAifNull.class);
return new CustomSerializer(annotation);
}
}
Witch supposed to write string "N/A" if the annotation is present and field is null. But method serialize is called only for not null fields.
Also, I have tried to call setNullValueSerializer:
#Override
public JsonSerializer<?> createContextual(SerializerProvider prov, BeanProperty property) {
NAifNull annotation = property.getAnnotation(NAifNull.class);
prov.setNullValueSerializer(new CustomNullSerializer(annotation));
return new CustomSerializer(annotation);
}
With such implementation:
private static class CustomNullSerializer extends JsonSerializer<Object> {
private final NAifNull annotation;
public CustomNullSerializer(NAifNull annotation) {
this.annotation = annotation;
}
#Override
public void serialize(Object value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
if (annotation != null) {
gen.writeString("N/A");
} else {
gen.writeNull();
}
}
}
But no result.
How to handle null fields in such way?
Update
According to discussion:
https://github.com/FasterXML/jackson-databind/issues/2057
prov.setNullValueSerializer(new CustomNullSerializer(annotation));
Is not supposed to be called from CreateContextual method.
Use a BeanSerializerModifier to customize the null serializer for a particular property:
public class CustomBeanSerializerModifier extends BeanSerializerModifier {
#Override
public List<BeanPropertyWriter> changeProperties(SerializationConfig config,
BeanDescription beanDesc, List<BeanPropertyWriter> beanProperties) {
for (BeanPropertyWriter beanProperty : beanProperties) {
if (beanProperty.getAnnotation(NAifNull.class) != null) {
beanProperty.assignNullSerializer(new CustomNullSerializer());
}
}
return beanProperties;
}
}
Where #NAifNull and CustomNullSerializer are define as follows:
public class CustomNullSerializer extends JsonSerializer<Object> {
#Override
public void serialize(Object value, JsonGenerator jgen,
SerializerProvider provider) throws IOException {
jgen.writeString("N/A");
}
}
#Target({ ElementType.FIELD })
#Retention(RetentionPolicy.RUNTIME)
#interface NAifNull {
}
Then use it as follows:
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new SimpleModule() {
#Override
public void setupModule(SetupContext context) {
super.setupModule(context);
context.addBeanSerializerModifier(new CustomBeanSerializerModifier());
}
});
If I understood you correctly, you want to write "N/A" to generated JSON, if the value is null.
Jackson docs states that value cannot be null. This is because the type parameter is Class object, which is constructed automatically by JVM.
As per this article, I think you could handle null fields with something like
public class CustomNullSerializer extends StdSerializer<Object> {
public CustomNullSerializer() {
this(null);
}
public CustomNullSerializer(Class<Object> t) {
super(t);
}
#Override
public void serialize(Object value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
gen.writeString("N/A");
}
}
And then use it with
prov.setNullValueSerializer(new CustomNullSerializer());
Thought I didn't try this myself, but I hope it helps.
UPDATE
Okey, now I had time to try this myself. I got it working with
ObjectMapper mapper...
mapper.getSerializerProvider().setNullValueSerializer(new CustomNullSerializer());

Jackson - Transform field value on serialization

In a Spring Boot applicaion with AngularJS frontend, a "Pin" field value has to be blackened on serialization, i.e., if the Pin field value is null in the POJO, the according JSON field has to remain blank; if the field value contains data, it has to be replaced with a "***" string.
Does Jackson provide a feature to get this done?
You can do it easily like following without any Custom Serializer
public class Pojo {
#JsonIgnore
private String pin;
#JsonProperty("pin")
public String getPin() {
if(pin == null) {
return "";
} else {
return "***";
}
}
#JsonProperty("pin")
public void setPin(String pin) {
this.pin = pin;
}
#JsonIgnore
public String getPinValue() {
return pin;
}
}
You can use Pojo.getPinValue() to get the exact value.
Try the following example.
public class Card {
public int id;
public String pin;
}
public class CardSerializer extends StdSerializer<Card> {
public CardSerializer() {
this(null);
}
public CardSerializer(Class<Card> t) {
super(t);
}
#Override
public void serialize(Card value, JsonGenerator jgen, SerializerProvider provider)
throws IOException, JsonProcessingException {
jgen.writeStartObject();
jgen.writeNumberField("id", value.id);
jgen.writeStringField("pin", "****");
jgen.writeEndObject();
}
}
Then you need to register your customer serializer with the ObjectMapper
Card card = new Card(1, "12345");
ObjectMapper mapper = new ObjectMapper();
 
SimpleModule module = new SimpleModule();
module.addSerializer(Card.class, new CardSerializer());
mapper.registerModule(module);
String serialized = mapper.writeValueAsString(card);
There are some improvements you can do here like registering the serializer directly on the class, but you can read more about it here Section 4 - http://www.baeldung.com/jackson-custom-serialization

What is the best way to do inline serialization of JSON data using JACKSON?

I've got a bean defined as such :
public static class TestBean {
private String a;
private Long b;
public TestBean(String a, Long b) {
this.a = a;
this.b = b;
}
public String getA() {
return a;
}
public Long getB() {
return b;
}
}
It models some business and I do not get to instantiate it (using JPA). Some of my API let the user retrieve a view of this bean serialize as JSON using Jackson (through JAX-RS) and I would like to add a list of related links to this view.
The normal Jackson JSON serialization would be (for a = "aa" and b = 2L) :
{"a":"aa","b":2}
And I would like to have the links appear as
{"a":"aa","b":2,
"links":[{"rel":"rel","href":"href://"},{"rel":"rel2","href":"href://2"}]}
Possible work-around
I would rather not add a getLinks() method to my bean, it's specific to this view.
Simply using a composite object would yield a serialization like :
{"data":{"a":"aa","b":2},"links":[{"rel":"rel","href":"href://"}]}
Which I could live with but is not what I was looking for ultimately.
Current solution
I would like to avoid manipulating the JSON string or having to reload it into a Map to insert my extra values. For now the solution I've come up with seem awfully convoluted:
Current scary solution :
//a composite view object
public abstract class AddedLinksView<K> {
private final K resource;
private final Link[] links;
public AddedLinksView(K resource) {
this.resource = resource;
links = buildLinks(resource);
}
public abstract Link[] buildLinks(K resource);
public K getResource() {
return resource;
}
public Link[] getLinks() {
return links;
}
}
//a specific bean serializer
private static class RawBeanSerializer extends BeanSerializer {
public RawBeanSerializer(BeanSerializerBase ser) {
super(ser);
}
//this is like the standard serialize but without the start and end tags
public void rawSerialize(Object bean, JsonGenerator jgen, SerializerProvider provider) throws IOException,
JsonGenerationException {
if (_propertyFilterId != null) {
serializeFieldsFiltered(bean, jgen, provider);
} else {
serializeFields(bean, jgen, provider);
}
}
}
#Test
public void usingModule() throws Exception {
// basic module metadata just includes name and version (both for troubleshooting; but name needs to be unique)
SimpleModule module = new SimpleModule("EnhancedDatesModule", new Version(0, 1, 0, "alpha"));
//adding a serializer for the composite view
module.addSerializer(new JsonSerializer<AddedLinksView>() {
#Override
public Class<AddedLinksView> handledType() {
return AddedLinksView.class;
}
#Override
public void serialize(AddedLinksView value, JsonGenerator jgen, SerializerProvider provider)
throws IOException, JsonProcessingException {
jgen.writeStartObject();
//looking up the bean serializer that will be used for my resource
JsonSerializer<Object> ser = provider.findTypedValueSerializer(value.getResource().getClass(), true,
null);
if (ser instanceof BeanSerializerBase) {
//cloning it in a sub class that makes it possible to 'inline' the serialization
RawBeanSerializer openSer = new RawBeanSerializer((BeanSerializerBase) ser);
openSer.rawSerialize(value.getResource(), jgen, provider);
}
//adding my links
jgen.writeArrayFieldStart("links");
for (Link link : value.getLinks()) {
jgen.writeObject(link);
}
jgen.writeEndArray();
jgen.writeEndObject();
}
});
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(module);
AddedLinksView<TestBean> view = new AddedLinksView<TestBean>(new TestBean("aa", 2L)) {
#Override
public Link[] buildLinks(TestBean resource) {
return new Link[] { new Link("rel", "href://"), new Link("rel2", "href://2") };
}
};
System.out.println("useModule json output: " + mapper.writeValueAsString(view));
}
Did I miss something obvious in Jackson to achieve this? Or am I completely off the mark in my requirements already?
There is no real way to externally inject things into POJOs to serialize: but you might be interested in checking out #JsonAnyGetter, which at least allows just adding contents of a java.util.Map as extra properties for a POJO.
Would this answer your question: http://wiki.fasterxml.com/JacksonFeatureUpdateValue
I am not sure you can avoid mapping. You may use Dozer to help.
This should help you: Tools for merging java beans

Categories