I have a Jackson annotated Java class
public class MyClass {
#JsonProperty String foo;
#JsonProperty Optional<Integer> bar;
}
This class is created by Spring Boot from the JSON of a HTTP request body. Some fields, such as bar, might not be present in the JSON. The JSON is not available at this stage.
I would like to fetch foo and bar given their string names, after checking whether they are present in the object.
void doSomething (MyClass obj) {
for (String s : {"foo", "bar"}) {
if (object_contains (obj, s)) {
Object value = get_value_by_name (obj, s);
// ...
}
}
}
Given that "foo" and "bar" are given as strings, how can I write object_contains and get_value_by_name?
Related
Suppose I make a get request to foo() with the JSON body:
{
„a“: „hallo“,
„b“: „world“,
„aa“: 10
}
#PostMapping("/foo")
public void foo(#RequestBody MyClass myClass) {
//do stuff
}
class MyClass {
String a;
String b;
String c;
int aa;
}
If the JSON object and the class match, then it works fine and you get a MyClass object with the correct variables set. But now I want to set only those fields in the MyClass class that I have also passed and the rest is initialized with null. How can I do this?
you have to change #GetMapping to #PostMapping if you want to use #RequestBody.
-Greetings :)
As mentioned here, I know that I can convert Java objects to JSON (with Jackson)
ObjectWriter ow = new ObjectMapper().writer().withDefaultPrettyPrinter();
String json = ow.writeValueAsString(objectToBeConverted);
I know that I can exclude fields from being included in the JSON string using the #JsonIgnore annotation, but what if I want to convert the same class to JSON multiple times, but each time choosing different fields to ignore?
For example, if I have a class
class Foo {
int a;
int b;
...
}
can I do something like
Foo foo = new Foo();
String json1 = ow.writeValueAsString(foo).excludeField('b');
String json2 = ow.writeValueAsString(foo).excludeField('a');
so that the resulting strings look like
// json1
{
a: 1234
}
// json2
{
b: 5678
}
If Jackson can't do it, maybe Gson can? Or another library?
You can try using different mix-in interfaces. I found two ways to do this.
Use methods for reading properties. You can then create a mix-in class that only defines the properties to exclude:
public interface ExludeA {
#JsonIgnore
int getA();
}
Use #JsonIncludeProperties to not tell which properties to exclude, but which properties to include:
#JsonIncludeProperties({ "b", "c" })
public interface ExludeA {
}
In both cases, add that mix-in to the object mapper:
objectMapper.addMixIn(Foo.class, ExcludeA.class);
There is one very, very important thing though - you must use a new ObjectMapper for each mix-in. If you use an ObjectMapper instance to serialize a Foo instance without mix-ins, then adding the mix-in won't help. That's probably because ObjectMapper instances cache some stuff.
Here is a simple approach if you could box up all primitive type in Foo.
For example: int -> Integer, boolean -> Boolean
#JsonInclude(Include.NON_NULL)
class Foo {
Integer a;
Integer b;
...
}
Then, just make a copy of Foo and set the property which you want to ignore to null.
Foo foo = new Foo();
foo.setA(1234);
foo.setB(5678);
Foo foo1 = objectMapper.readValue(objectMapper.writeValueAsString(foo), Foo.class); // make a copy of Foo
foo1.setB(null); // force to ignore B
String json1 = ow.writeValueAsString(foo1); // it will be {a:1234}
There is multiple solution based on your need:
First:
You can just define two different DTO for your purpose and every time you need to each one just use it.
Second:
You can use #JsonInclude(JsonInclude.Include.NON_NULL) annotation for the properties:
class Foo {
#JsonInclude(JsonInclude.Include.NON_NULL)
int a;
#JsonInclude(JsonInclude.Include.NON_NULL)
int b;
}
P.S: You can use this annotation on class level as:
#JsonInclude(JsonInclude.Include.NON_NULL)
class Foo {
int a;
int b;
}
Third:
Use can define a filter to ignore properties based on different conditions.
Define a simple class for your filter:
public class YourConditionalFilter {
#Override
public boolean equals(int a) {
return a == 1234;
}
}
And then add this filter as annotation on top of the property:
#JsonInclude(value = JsonInclude.Include.CUSTOM, valueFilter = YourConditionalFilter.class)
int a;
I have a POJO that contains the following attributes
public class Example {
#JsonProperty("inputFoo")
private String foo
#JsonProperty("inputBar")
private String bar
#JsonProperty("inputBaz")
#JsonDeserialize(using = MyDeserializer.class)
private Set<String> baz
}
The JSON that I am working with to represent this data currently represents the baz attribute as a single string:
{"inputFoo":"a", "inputBar":"b", "inputBaz":"c"}
I am using the Jackson ObjectMapper to attempt to convert the JSON to my POJO. I know that the input baz String from the JSON wont map cleanly to the Set that I am trying to represent it as, so I defined a custom Deserializer:
public class MyDeserializer extends StdDeserializer<Set<String>> {
public MyDeserializer(){}
public MyDeserializer(Class<?> vc) {
super(vc);
}
public Set<String> deserialize(JsonParser p, DeserializationContext cxt) throws IOException, JsonProcessingException {
String input = p.readValueAs(String.class);
Set<String> output = new HashSet<>();
if(input != null) {
output.add(input);
}
return output;
}
}
I am getting an IllegalArgumentException referencing the "inputBaz" attribute, which I can provide details on. Does anyone see any obvious issue with my deserializer implementation? Thanks
You do not need to implement custom deserialiser, use ACCEPT_SINGLE_VALUE_AS_ARRAY feature. It works for sets as well:
Feature that determines whether it is acceptable to coerce non-array
(in JSON) values to work with Java collection (arrays,
java.util.Collection) types. If enabled, collection deserializers will
try to handle non-array values as if they had "implicit" surrounding
JSON array. This feature is meant to be used for
compatibility/interoperability reasons, to work with packages (such as
XML-to-JSON converters) that leave out JSON array in cases where there
is just a single element in array. Feature is disabled by default.
See also:
com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize instance of java.util.ArrayList out of START_OBJECT token
Replace the 2 constructors with this no-arg constructor:
public MyDeserializer() {
super(TypeFactory.defaultInstance().constructCollectionType(Set.class, String.class));
}
ACCEPT_SINGLE_VALUE_AS_ARRAY as suggested is a good option.
Maybe your actual problem is more complicated but if not you could also try #JsonCreator instead of custom deserializer. Like:
public class Example {
#JsonCreator
public Example(#JsonProperty("inputFoo") String foo,
#JsonProperty("inputBar") String bar,
#JsonProperty("inputBaz") String strBaz) {
this.foo = foo;
this.bar = bar;
this.baz = new HashSet<>();
baz.add(strBaz);
}
private String foo;
private String bar;
private Set<String> baz;
}
Just to show that in more general case you might avoid implementing custom deserializer with #JsonCreator also but still make some simple conversions.
Say I have classes Foo
public class Foo {
private Bar bar;
}
and Bar
public class Bar {
private String fizz;
private String bang;
}
EDIT: For clarification I do not own Foo and Bar and cannot alter these classes.
If I want to serialize an empty object of type Foo, it's member, which is of type Bar, will be returned as null.
String json = objectMapper.writeValueAsString(new Foo()); // "{"bar" : null}"
Is there any way I can get the object mapper to serialize an empty Bar object without having to instantiate a new instance of Bar and then adding it to a new instance of Foo?
String json = objectMapper.writeValueAsString(new Foo()) // "{bar": {"fizz" : null, "bang" : null } }"
I was also required to produce such a structure for legacy client compatibility, here is my solution (depends on Spring Boot since uses #JsonComponent annotation)
Create "special object" that will be treated as empty
public class EmptyObject {
}
Create property in your model
#JsonProperty("data")
private EmptyObject data = new EmptyObject();
public EmptyObject getData() {
return data;
}
Create serializer that will process empty object above
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
import com.sevensenders.datahub.api.service.response.model.EmptyObject;
import org.springframework.boot.jackson.JsonComponent;
import java.io.IOException;
#JsonComponent
public class EmptyObjectSerializer extends StdSerializer<EmptyObject> {
public EmptyObjectSerializer() {
this(null);
}
public EmptyObjectSerializer(Class<EmptyObject> t) {
super(t);
}
#Override
public void serialize(EmptyObject value, JsonGenerator gen, SerializerProvider provider) throws IOException {
// to maintain AF compatible format it is required to write {} instead of null
gen.writeStartObject();
gen.writeEndObject();
}
}
Output:
{
...
"data": {}
}
You could create a custom serializer for serializing Foo objects. Then in your custom FooSerializer implementation, you could check for a null bar value and serialize it as a default Bar instance. See https://spin.atomicobject.com/2016/07/01/custom-serializer-jackson/ or http://www.baeldung.com/jackson-custom-serialization for some examples of how to create custom serializers.
It's a bit unrelated to this, but if you define members as private on data class in Kotlin, then, Jackson serializer will produce empty json such as {}.
If you don't want to write your own serializer you can use this approach of declaring type of field as ObjectNode:
private ObjectNode data;
You can set/initialize it like this:
data = new ObjectNode(JsonNodeFactory.instance)
No. I don't see any way doing this. If you don't initialize your Bar, it'll be null inside the JSON.
Since you can't alter these classes, you can just check if the Bar inside the Foo is null and if it is, just initialize it and you'll get what you want.
Bar bar = foo.getBar();
if (bar == null) {
foo.setBar(new Bar());
}
String json = objectMapper.writeValueAsString(foo);
The json will be the following:
{
"bar" : {
"fizz" : null,
"bang" : null
}
}
Hope this helps.
Say I have classes Foo
public class Foo {
private Bar bar;
}
and Bar
public class Bar {
private String fizz;
private String bang;
}
EDIT: For clarification I do not own Foo and Bar and cannot alter these classes.
If I want to serialize an empty object of type Foo, it's member, which is of type Bar, will be returned as null.
String json = objectMapper.writeValueAsString(new Foo()); // "{"bar" : null}"
Is there any way I can get the object mapper to serialize an empty Bar object without having to instantiate a new instance of Bar and then adding it to a new instance of Foo?
String json = objectMapper.writeValueAsString(new Foo()) // "{bar": {"fizz" : null, "bang" : null } }"
I was also required to produce such a structure for legacy client compatibility, here is my solution (depends on Spring Boot since uses #JsonComponent annotation)
Create "special object" that will be treated as empty
public class EmptyObject {
}
Create property in your model
#JsonProperty("data")
private EmptyObject data = new EmptyObject();
public EmptyObject getData() {
return data;
}
Create serializer that will process empty object above
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
import com.sevensenders.datahub.api.service.response.model.EmptyObject;
import org.springframework.boot.jackson.JsonComponent;
import java.io.IOException;
#JsonComponent
public class EmptyObjectSerializer extends StdSerializer<EmptyObject> {
public EmptyObjectSerializer() {
this(null);
}
public EmptyObjectSerializer(Class<EmptyObject> t) {
super(t);
}
#Override
public void serialize(EmptyObject value, JsonGenerator gen, SerializerProvider provider) throws IOException {
// to maintain AF compatible format it is required to write {} instead of null
gen.writeStartObject();
gen.writeEndObject();
}
}
Output:
{
...
"data": {}
}
You could create a custom serializer for serializing Foo objects. Then in your custom FooSerializer implementation, you could check for a null bar value and serialize it as a default Bar instance. See https://spin.atomicobject.com/2016/07/01/custom-serializer-jackson/ or http://www.baeldung.com/jackson-custom-serialization for some examples of how to create custom serializers.
It's a bit unrelated to this, but if you define members as private on data class in Kotlin, then, Jackson serializer will produce empty json such as {}.
If you don't want to write your own serializer you can use this approach of declaring type of field as ObjectNode:
private ObjectNode data;
You can set/initialize it like this:
data = new ObjectNode(JsonNodeFactory.instance)
No. I don't see any way doing this. If you don't initialize your Bar, it'll be null inside the JSON.
Since you can't alter these classes, you can just check if the Bar inside the Foo is null and if it is, just initialize it and you'll get what you want.
Bar bar = foo.getBar();
if (bar == null) {
foo.setBar(new Bar());
}
String json = objectMapper.writeValueAsString(foo);
The json will be the following:
{
"bar" : {
"fizz" : null,
"bang" : null
}
}
Hope this helps.