Jackson custom complex deserializer into classes - java

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.

Related

How to populate a DeserializationContext with domain-specific objects so they can be used during deserialisation?

DOMAIN
There are two classes ProjectSchema and PageSchema. The latter is dependent on the former.
class ProjectSchema {}
class PageSchema {
public PageSchema(ProjectSchema schema) {}
}
These classes represent JSON configuration files and both have custom JsonDeserializers.
class PageSchemaDeserialiser extends JsonDeserializer<PageSchema> {
#Override
public PageSchema deserialize(JsonParser parser, DeserializationContext context) {}
}
PROBLEM
To completely initialise a PageSchema object in the PageSchemaDeserialiser, I need an instance of ProjectSchema there.
When I start parsing a TreeNode into a PageSchema, I have a ProjectSchema constructed
ProjectSchema projectSchema = ...;
...
new ObjectMapper().treeToValue(node, PageSchema.class);
and I want to let the deserialiser know about it. I saw there is DeserializationContext the deserialize method takes. I am wondering if I am able to pass the project schema there, so it will be available during page schema construction.
QUESTION
That boils down to a question:
How to populate a DeserializationContext with domain-specific objects so they can be used during deserialisation?
It took me some time before I found out that DeserializationContext contains InjectableValues and I can pass them in through an ObjectMapper.
ObjectMapper mapper = new ObjectMapper();
mapper.setInjectableValues(new InjectableValues.Std(myMap));
PageSchema schema = mapper.readValue(inputStream, PageSchema.class);
In PageSchemaDeserialiser, I call
Object myValue = context.findInjectableValue("myKey", null, null);
It solves the problem, yet brings a new one. I now should keep track of InjectableValues in my shared ObjectMapper instance.

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<>();
}
}

Using Jackson JSON Generator, how can I write multiple objects to one field?

Suppose I have the following three classes (getters and setters left out for brevity):
#JsonAutoDetect
public class InfoCollection{
private InfoType1 info1;
private InfoType2 info2;
}
#JsonAutoDetect
public class InfoType1{
private String fieldA;
}
#JsonAutoDetect
public class InfoType2{
private String fieldB;
}
I"m trying to write a JsonSerializer.serialize() function that serializes an InfoCollection object in this format:
{
"allInfo":{
"fieldA":"foo",
"fieldB":"bar"
}
}
This is what I have now:
jsonGenerator.writeStartObject();
jsonGenerator.writeFieldName("allInfo");
jsonGenerator.writeObject(myInfoCollection.getInfo1());
jsonGenerator.writeObject(myInfoCollection.getInfo2());
jsonGenerator.writeEndObject();
which is causing the following exception:
org.codehaus.jackson.JsonGenerationException: Can not start an object, expecting field name
Am I missing something small or am I totally going about this the wrong way?
NOTE: A couple of the proposed solutions so far involve writing each individual field of InfoType1 and InfoType2. I am looking for a solution that does not require this because I'd like to use the solution on huge classes with many fields.
Instead of calling writeFieldName("allInfo") you should call writeObjectFieldStart("allInfo") because "allInfo" is another JSON object. So your custom serializer should look the following way:
public void serialize(InfoCollection infoCollection, JsonGenerator jgen, SerializerProvider provider) throws IOException{
jgen.writeStartObject();
jgen.writeObjectFieldStart("allInfo");
jgen.writeObjectField("fieldA", infoCollection.getInfo1().getFieldA());
jgen.writeObjectField("fieldB", infoCollection.getInfo2().getFieldB());
jgen.writeEndObject();
jgen.writeEndObject();
}
Or you may try annotation based approach:
#JsonRootName("allInfo")
public class InfoCollection {
#JsonUnwrapped
private InfoType1 info1;
#JsonUnwrapped
private InfoType2 info2;
/* getters, setters */
}
(You need to enable SerializationConfig.Feature.WRAP_ROOT_VALUE feature in order for this to work. See Serialization features)
In the future, when you have a stack trace, let us know in which line the problem shows up.
That said, the fix is probably:
jsonGenerator.writeStartObject();
jsonGenerator.writeFieldName("allInfo");
jsonGenerator.writeStartObject(); // start nested object
jsonGenerator.writeFieldName("fieldA"); // start field
jsonGenerator.writeObject(myInfoCollection.getInfo1().fieldA);
jsonGenerator.writeFieldName("fieldB"); // start fieldB
jsonGenerator.writeObject(myInfoCollection.getInfo2().fieldB);
jsonGenerator.writeEndObject(); // end nested object
jsonGenerator.writeEndObject();
Solution using a wrapper object:
#JsonAutoDetect
public class Wrapper {
private transient InfoCollection data; // transient makes Jackson ignore this
public String getFieldA() { return data.info1.fieldA; }
public String getFieldB() { return data.info1.fieldB; }
}
That makes Jackson see only what you want and how you want it.
Alternatively, use reflection to recursively collect all fields and their names:
List<Pair<String, Object>> data = collectFields( myInfoCollection );
collectFields should examine all fields and add everything to the list which is either a primitive or, say, where field.getType().getName().startsWith("java.lang") or any other rules you need.
If the field is a reference, call collectFields() recursively.
When you have the list, just call jsonGenerator in a loop to write the results.

Jackson vs Gson for simple deserialisation

For parsing JSON like this twitter API users/show response I've been using Jackson and Gson Java libraries as candidates to do this work. I'm only interested in a small subset of properties of the JSON so Gson was nice because of its very concise syntax but I'm losing an internal battle to continue to use Gson as Jackson is already used elsewhere in our application and it has documented better performance (which I concede are both good reasons to lose Gson).
For a POJO like
public class TwitterUser {
private String id_str;
private String screen_name;
public String getId_str() {
return id_str;
}
public void setId_str(String id_str) {
this.id_str = id_str;
}
public String getScreen_name() {
return screen_name;
}
public void setScreen_name(String screen_name) {
this.screen_name = screen_name;
}
}
The only code for Gson needed to build this is one line,
TwitterUser user = new Gson().fromJson(jsonStr, TwitterUser.class);
That's pretty nice to me; scales well and is opt-in for the properties you want. Jackson on the other hand is a little more laborious for building a POJO from selected fields.
Map<String,Object> userData = new ObjectMapper().readValue(jsonStr, Map.class);
//then build TwitterUser manually
or
TwitterUser user = new ObjectMapper().readValue(jsonStr, TwitterUser.class);
//each unused property must be marked as ignorable. Yikes! For 30 odd ignored fields thats too much configuration.
So after that long winded explanation, is there a way I can use Jackson with less code than is demonstrated above?
With Jackson 1.4+ you can use the class-level #JsonIgnoreProperties annotation to silently ignore unknown fields, with ignoreUnknown set to true.
#JsonIgnoreProperties(ignoreUnknown = true)
public class TwitterUser {
// snip...
}
http://wiki.fasterxml.com/JacksonAnnotations
http://wiki.fasterxml.com/JacksonHowToIgnoreUnknown

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