Use multiple #JsonView (Jackson) in JAX-RS - java

I want to use multiple JsonViews to serialize a response.
For example, I have the object below:
public class A {
#JsonView(FieldView.A.class)
private String a;
#JsonView(FieldView.B.class)
private String b;
#JsonView(FieldView.C.class)
private String c;
}
And I want to add on the serialization the views A and B. But I did not find any solution for that , because I'm using ObjectMapper writeWithView that accepts just one view (Not a List), this way if I add A then B and C are removed from the response.
om.writeWithView(FieldView.A.class)
Is that a way to writeWithView using more than one view?
Thanks

After all I did a workaround. Using SimpleBeanPropertyFilter I created a list of the fields I want to discard with serializeAllExcept(Set<String> fields).
ObjectMapper mapper = new ObjectMapper();
SimpleBeanPropertyFilter theFilter = SimpleBeanPropertyFilter.serializeAllExcept(fields);
FilterProvider filters = new SimpleFilterProvider().addFilter("myFilter", theFilter);
mapper.setFilterProvider(filters);

Related

Jackson custom complex deserializer into classes

I need to use custom deserializer for my json string. The problem is that my class I want to serialize in is very complex and constructed from other classes. But all of the examples in the internet show only very basic way of deserializing jsons (i.e. only retrieving one value by name and getting it's value), but I have subclasses and want to use them so I don't need to write manually all of the mapping. Is there any way I can do this? Example to understand what I'm talking about, so let's say I have this structure:
public TestClass{
public Class1 first;
public Class2 second;
...
public Class10 ten;
}
And all of the classes contain the data, something like this:
public Class1{
public String name;
public int id;
...
}
Obviously I don't want to manually map all of that, but all of the examples in the internet show exactly that, is there any way to read the values into classes directly without needing doing manual mapping? For example most common example is to do something like this:
#Override
public Item deserialize(JsonParser jp, DeserializationContext ctxt)
throws IOException, JsonProcessingException {
JsonNode node = jp.getCodec().readTree(jp);
int id = (Integer) ((IntNode) node.get("id")).numberValue();
String itemName = node.get("itemName").asText();
int userId = (Integer) ((IntNode) node.get("createdBy")).numberValue();
return new Item(id, itemName, new User(userId, null));
}
So my question is can I do this more easily, i.e. reading values directly into these classes? (Except the one, that's why I need the custom deserilizing at the first place, but it's better to write only one manual mapping instead of 10).
Summurizing, I want to do something like this:
#Override
public Item deserialize(JsonParser jp, DeserializationContext ctxt)
throws IOException, JsonProcessingException {
...
Testclass test = new Testclass();
Class1 class1 = json...parse(..., Class1.class);
Class2 class2 = json...parse(..., Class2.class);
...
test.setClass1(class1);
test.setClass2(class2);
...
Class10 manualClass = new Class10();
manualClass.setField1(json.get("class10").get("field1").stringValue());
...
test.setClass10(manualClass);
}
Obviously you can just use
ObjectMapper objectMapper = new ObjectMapper();
String json = "..." //some json from any source with complex structure related to TestClass structure
TestClass result = objectMapper.readValue(json, TestClass.class)
Usually it is enough for many cases. Any custom deserilizer depends on what do you want to change in the default deserializer. Anyway we can help with customization if you clarify your case with json example
The answer by Oleg is a good one for what you want to do.
There is a little known feature with Jackson called Mixins. They allow you to provide overrides, annotations and any other desired modifications just to specific places in your complex object graph. This means that you can customize specific aspects of the serialization without having to implement a whole new serializer.
ObjectMapper mapper = new ObjectMapper();
mapper.addMixInAnnotations(Class1.class, Class1Mixin.class);
public Class1
{
public String name;
public int id;
}
public abstract Class1Mixin
{
// Override 'name' to serialize as 'fullName'. Leave the id the same.
#JsonProperty("fullName")
public String name;
}
Think of if as being able to overlay annotations on top of little sections of the object graph without polluting your original classes. Super handy for keeping Models clean and using Mixins to specify how to make them Data Transfer Objects(DTO)/Messages.

Use custom object in Jackson constructor

Is there a way to provide the Jackson Deserializer with a default value from "the outside" (e.g. DI container) that it will use when deserializing an object, in this case tagRegistry?
#JsonCreator
public ExtractionRule(#JsonProperty("id") String id,
TagRegistry tagRegistry) {
this.id = id;
this.tagRegistry = tagRegistry;
}
I couldn't find an easy way to do this.
You could try #JacksonInject. Add this member to the ExtractionRule class:
#JacksonInject("tagRegistry")
private TagRegistry tagRegistry;
And inject the tagRegistry to the ObjectMapper before deserialization:
InjectableValues.Std injectableValues = new InjectableValues.Std();
injectableValues.addValue("tagRegistry", tagRegistry);
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setInjectableValues(injectableValues);
I haven't tried using it in a constructor, not sure if that works.
You can find further examples here:
https://www.logicbig.com/tutorials/misc/jackson/jackson-inject.html
https://www.concretepage.com/jackson-api/jackson-jacksoninject-example#JacksonInject

How to serialize collection as empty list depending on runtime condition with jackson

We have a business requirement that elements of child collections of entities (we use JPA) in our spring-boot application shouldn't be visible in rest api if the user doesn't have permissions to view child entity.
Right now we use AOP to wrap all get methods in our services so that they do something like this if (!allowed("ChildView")) {entity.setChildren(new ArrayList<>())} which doesn't seems like a good solution to me for a few reasons. First of all relationship between permission name and collections setter is hardcoded outside of entity. Also modifying actual object because we don't want to show something about it in REST api seems kind of strange. You don't remove something if you don't want to show it. You can just hide it. So I thought why not hide it when serializing?
So I can see how to ignore properties completely at runtime via Mixin and #JsonIgnore but I can't find how to return empty list instead.
Ideally I thing of an API like that.
class Entity {
#OneToMany
#AuthSerialize("ChildView", default=Collections.emptyList())
Collection<Child> children;
}
Current solution looks something like this.
Map<Class<? extends BaseEntity>, Map<String, Consumer<BaseEntity>> protectors;
process(BaseEntity e) {
protectors.getOrDefault(e.getClass(), Collectoions.emptyMap())).forEach((permission, clearer) ->
if !allowed(permission) clearer.accept(e)
)
I think the "not wasting cycles" is over-engineering. It might be a valid assertion if you're serializing a million entities per second. Otherwise the JVM will optimize the "hot spot" for you. And anyway, that won't be the bottleneck in your application architecture.
If you know your entities have a "children" array field in common, you might want to apply the same JsonSerializer to all of them, by simply maintining a Map of the compatible classes.
You have to understand that Jackson has its own limitations. If you need something more than that, you might want a totally custom solution. This is the best you can obtain with Jackson.
Hope the answer is satisfactory.
You can use a custom JsonSerializer<T>.
class EntitySerializer extends StdSerializer<Entity> {
private static final long serialVersionUID = 1L;
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
EntitySerializer() {
super(Entity.class);
}
#Override
public void serialize(
final Entity value,
final JsonGenerator generator,
final SerializerProvider provider) throws IOException {
final TreeNode jsonNode = OBJECT_MAPPER.valueToTree(value);
if (!AuthUtils.allowed("ChildView")) {
final TreeNode children = jsonNode.get("children");
if (children.isArray()) {
((ContainerNode<ArrayNode>) children).removeAll();
}
}
generator.writeTree(jsonNode);
}
}
However, as you can see we are using an ObjectMapper instance inside our JsonSerializer (or would you prefer manually "writing" each field with JsonGenerator? I don't think so :P). Since ObjectMapper looks for annotations, to avoid infinite recursion of the serialization process, you have to ditch the class annotation
#JsonSerialize(using = EntitySerializer.class)
And register the custom JsonSerializer manually to the Jackson ObjectMapper.
final SimpleModule module = new SimpleModule();
module.setSerializerModifier(new BeanSerializerModifier() {
#Override
public JsonSerializer<?> modifySerializer(
final SerializationConfig config,
final BeanDescription beanDesc,
final JsonSerializer<?> serializer) {
final Class<?> beanClass = beanDesc.getBeanClass();
return beanClass == Entity.class ? new EntitySerializer() : serializer;
}
});
final ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(module);
Finally, you just have to use the ObjectMapper, or let your framework use it.
As you're using Spring, you can register a #Bean of type ObjectMapper, marked as #Primary, or you can register a #Bean of type Jackson2ObjectMapperBuilder.
Previous answer.
As the allowed method is static, that means it can be accessed from "everywhere".
After fiddling a little bit with Jackson, I'll give you the first of the two options, as I'm still working on the second one.
Annotate your class with
#JsonSerialize(converter = EntityConverter.class)
public class Entity { ... }
Here you're specifying a custom Converter.
The Converter implementation is pretty neat.
Inside the static block I'm simply getting the Auth annotation value, but that is optional, you can do what you feel like is best for your usecase.
class EntityConverter extends StdConverter<Entity, Entity> {
private static final String AUTH_VALUE;
static {
final String value;
try {
final Field children = Entity.class.getDeclaredField("children");
final AuthSerialize auth = children.getAnnotation(AuthSerialize.class);
value = auth != null ? auth.value() : null;
} catch (final NoSuchFieldException e) {
// Provide appropriate Exception, or handle it
throw new RuntimeException(e);
}
AUTH_VALUE = value;
}
#Override
public Entity convert(final Entity value) {
if (AUTH_VALUE != null) {
if (!AuthUtils.allowed(AUTH_VALUE)) {
value.children.clear();
}
}
return value;
}
}
Let me know if this is sufficient, or you'd prefer a more complex solution.
You could use the Mixin to override the getter method:
class noChildViewEntity {
public Collection<Child> getChildren() {
return new ArrayList<>();
}
}

Jackson converting Map<CustomEntity, Integer> to JSON creates toString() from CustomEntity

I have custom Entity that i want to put as Json to my view page
But when i serialize it in map using ObjectMapper from Jackson i receive String created from toString() method
#Test
public void test() throws JsonProcessingException {
Map<ProductEntity, Integer> map = new HashMap<ProductEntity, Integer>();
ProductEntity prod = new ProductEntity();
prod.setIdProduct(1);
map.put(prod, 1);
ObjectMapper mapper = new ObjectMapper();
System.out.println(mapper.writeValueAsString(map));
}
Received: {"com.onlineshop.entity.ProductEntity#2":1}
where "com.onlineshop.entity.ProductEntity#2" is a String, not an object.
So how can i make it to be an Object?
I need exactly Map, not another type of Collection
You either need to annotate your ProductEntity object so Jackson knows how to serialize it or use a Mix In annotation if you are not able to modify the ProductEntity class. IIRC there are also global Jackson options you can set that tell it how to handle POJOs.
Since you didn't specify which version of Jackson you're using I can't link to the correct documents but there is a ton of information available on the Jackson sites on how to use annotations and mix ins.
Thanks to all for your answers.
I solved it by creating new DTO which contains :
private ProductEntity
private Integer
fields.

Is it possible to have a Jackson custom deserializer with non-default constructor?

Lets say that I have the following classes:
public class Person {
String name;
Set<Department> departments;
}
public class Department {
String code;
String name;
}
So I want to write a custom Department deserializer in order to annotate the deparments field in the Person class to use it. Because this custom deserializer will only be used to deserialize Department objects that are inside a Person object. The problem is that my custom Department deserializer will need to have a DepartmentRepository that must be passed in the deserializer's constructor. How can I do this? Is this possible? I don't want to register the deserializer in the object mapper because it must only be used when the deparatments field from the Person class gets deserialized.
UPDATE: What I need is, apart from annotate the departments field with JsonDeserialize annotation with the parameter contentUsing = MyCustomDepartmentDeserializer.class, is a way to tell Jackson that when it creates a MyCustomDepartmentDeserializer object, it must done it by calling a constructor that receives a DepartmentRepository. The deserializer may be something like this:
public class MyCustomDepartmentDeserializer extends JsonDeserializer<Department> {
private final DepartmentRepository departmentRepository;
public MyCustomDepartmentDeserializer(DepartmentRepository departmentRepository) {
this.departmentRepository = departmentRepository;
}
#Override
public Department deserialize(JsonParser jp, DeserializationContext ctxt)
throws IOException, JsonProcessingException {
//IMPLEMENTATION!
}
}
First things first: to specify deserializer to use for contents of an array you can use
#JsonDeserialize(contentUsing=MyDeserializer.class)
Set<Department> departments;
to specify deserializer to use for contents of the collection in question.
As to ability to use non-default constructors, #JsonCreator allows this.
But to pass a context object, you need Jackson 1.9 may be your friend (see "Jackson 1.9 overview"), which allows "injection" of objects outside of JSON.
You can then mix and match injectable values and JSON properties, for example:
public class POJO {
#JsonCreator // can also be used for static factory methods
public POJO(#JacksonInject DepartmentRepository repo, #JsonProperty("value") int value) {
....
}
}
This might be enough to do what you are asking.
You can add a custom serializer/deserializer with a non-default constructor by registering it as a module with you ObjectMapper.
SimpleModule simpleModule = new SimpleModule();
JsonDeserializer<MyObject> customDeserializer = new CustomDeserializer("Blah");
simpleModule.addDeserializer(MyObject.class, customDeserializer);
mapper.registerModule(simpleModule);
You should also remove the annotation in the MyObject class if it's there.
Here is a deserializer I just wrote. Note the use of a non-default constructor.
public class SparseStringArrayVectorDeserializer extends JsonDeserializer<SparseStringArrayVector> {
#Override
public SparseStringArrayVector deserialize(JsonParser jp, DeserializationContext ctxt)
throws IOException, JsonProcessingException {
/* This isn't the most efficient way to do this, since we're building a tree of nodes that we will discard.
* However, we need to change the order around, so something like this is hard to avoid.
*/
JsonNode tree = jp.readValueAsTree();
int tokenCount = tree.size();
int[] indexes = new int[tokenCount];
String[][] strings = new String[tokenCount][];
Iterator<Entry<String, JsonNode>> fieldNameIt = tree.getFields();
int slot = 0;
while (fieldNameIt.hasNext()) {
Entry<String, JsonNode> entry = fieldNameIt.next();
int index = Integer.parseInt(entry.getKey());
indexes[slot] = index;
String[] thisTokenStrings = new String[entry.getValue().size()];
for (int x = 0; x < thisTokenStrings.length; x++) {
thisTokenStrings[x] = entry.getValue().get(x).getTextValue();
}
strings[slot] = thisTokenStrings;
slot++;
}
return new SparseStringArrayVector(indexes, strings);
}
}
Used with the following. Note that you could have any constructor pattern that you like when creating the deserializer and adding it to the module.
ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule("ResultAccess", new Version(7, 4, 0, null));
module.addDeserializer(SparseStringArrayVector.class, new SparseStringArrayVectorDeserializer());
module.addDeserializer(AbstractResultAccess.class, new ProxyAbstractResultAccessDeserializer());
mapper.registerModule(module);
No, at the very beginning, you can go without specify a custom deserializer; Jackson can detect your nested field and map them correctly, only when all the model classes implements Serializable.
So, add implements Serializable to Department and Person, and you will see Jackson works out of the box.
Just off the top of my head, I am pretty sure you can do that using the annotations in Jackson to identify which properties you want to exposure.

Categories

Resources