Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to set jackson serialization depth level when using ObjectMapper?

Assuem that i have this below classes:

public class Employee {
    private Department department;

    // other fields, getters and setters omited for brevtity
}

public class Department {
    private Address address;

    // other fields, getters and setters omited for brevtity
}

public class Address {
    private Location location;

    // other fields, getters and setters omited for brevtity
}

public class Location {
    private String streetName;

    // other fields, getters and setters omited for brevtity
}

now, i want to load Employee object and serialize it with ObjectMapper:

public void serializeEmployee() {
    Employee employee = entityManager.load(Employee.class, 1);
    ObjectMapper mapper = new ObjectMapper();
    System.out.println(mapper.writeValueAsString(student));
}

when i run above code, i see json string like this:

{
    "department" : {
        "address" : {
            "location" : {
                "streetName" : {}
            }
        }
    }
}

but i want to set serialization depth to one level, i mean when the code run, i want to see result like this:

{
    "department" : {
    }
}

NOTE

I don't want to use jackson annotations, i want to set configuration when using mapper object. for exmaple with calling mapper.setConfig or mapper.disable.

like image 774
Rasool Ghafari Avatar asked Dec 20 '17 14:12

Rasool Ghafari


1 Answers

You can use PropertyFilter to be able to limit the depth of serialization. Here's a solution I came up with.couple of things to note when using this filter, which are also demonstrated in the example:

  • arrays provide an additional depth level, so array entry is depth+2 from parent (it's not a bug, it's a feature :D - this behavior could be changed if context is parsed for array start)
  • map properties are serialized fully at the level the map is declared
  • depth is defined per-class; you could extend base class with DN suffix to serialize if you need variable length, or make a constant filter name and create a dedicated ObjectMapper for each depth.

Here's the code. The DeepFieldFilter does depth-calculation. It has to be registered as a filter in ObjectMapper and the data class has to marked with @JsonFilter annotation.

public class JsonSerializationDeepFun {

    @Data
    @JsonFilter("depth_3")
    static class DynamicJsonObject {
        Long id;
        String name;
        BigDecimal price;

        List<DynamicJsonObject> children = new ArrayList<>();

        @JsonIgnore
        Map<String, Object> properties = new HashMap<>();

        @JsonAnySetter
        public void add(String key, String value) {
            properties.put(key, value);
        }

        @JsonAnyGetter
        public Map<String, Object> getMap() {
            return properties;
        }
    }


     /**
     * There're a couple of things to note when using this filter. <a href="https://stackoverflow.com/a/51279460/1961634">Visit stackoverflow for an example</a>
     * <ul>
     * <li>arrays provide an additional depth level, so array entry is depth+2
     * from parent; it's not a bug, it's a feature - this behavior could be
     * changed if JsonStreamContext is parsed for array start</li>
     * <li>map properties are serialized fully at the level the map is declared</li>
     * <li>depth is defined per-class; you could extend base class with DN suffix
     * to serialize if you need variable length, or make a constant filter name
     * and create a dedicated `ObjectMapper` for each depth</li>
     * </ul>
     * @author Dariusz Wawer <[email protected]>
     *
     */
    static class DeepFieldFilter extends SimpleBeanPropertyFilter {
        private final int maxDepth;

        public DeepFieldFilter(int maxDepth) {
            super();
            this.maxDepth = maxDepth;
        }

        private int calcDepth(PropertyWriter writer, JsonGenerator jgen) {
            JsonStreamContext sc = jgen.getOutputContext();
            int depth = -1;
            while (sc != null) {
                sc = sc.getParent();
                depth++;
            }
            return depth;
        }

        @Override
        public void serializeAsField(Object pojo, JsonGenerator gen, SerializerProvider provider, PropertyWriter writer)
                                        throws Exception {
            int depth = calcDepth(writer, gen);
            if (depth <= maxDepth) {
                writer.serializeAsField(pojo, gen, provider);
            }
            // comment this if you don't want {} placeholders
            else {
                writer.serializeAsOmittedField(pojo, gen, provider);
            }
        }

    }

    public static void main(String[] args) throws IOException {
        ObjectMapper om = new ObjectMapper();

        SimpleFilterProvider depthFilters = new SimpleFilterProvider().addFilter("depth_1", new DeepFieldFilter(1))
                                        .addFilter("depth_2", new DeepFieldFilter(2))
                                        .addFilter("depth_3", new DeepFieldFilter(3))
                                        .addFilter("depth_4", new DeepFieldFilter(4))
                                        .addFilter("depth_5", new DeepFieldFilter(5))
        // ...
        ;
        om.setFilterProvider(depthFilters);

        om.enable(SerializationFeature.INDENT_OUTPUT);
        DynamicJsonObject obj = new DynamicJsonObject();
        obj.setId(321L);
        obj.setName("name");
        obj.setPrice(BigDecimal.valueOf(10.0));

        Map<String, Object> mapD3 = new HashMap<>();
        mapD3.put("depth", "3");
        mapD3.put("info", "gets serialzied at depth 1");

        Map<String, Object> mapD2 = new HashMap<>();
        mapD2.put("depth", "2");
        mapD2.put("map", mapD3);

        Map<String, Object> mapD1 = new HashMap<>();
        mapD1.put("depth", "1");
        mapD1.put("map", mapD2);

        obj.setProperties(mapD1);

        DynamicJsonObject child = new DynamicJsonObject();
        child.setId(514L);
        child.setName("actually depth 3, because array");
        child.setPrice(BigDecimal.valueOf(5.1));
        obj.getChildren().add(child);

        String jsonStr = om.writeValueAsString(obj);
        System.out.println(jsonStr);
    }

}
like image 61
Dariusz Avatar answered Nov 15 '22 09:11

Dariusz