From 5b7f0fe1266da6191314ebbfeeb7c988fed70e8a Mon Sep 17 00:00:00 2001 From: asmhatre Date: Tue, 4 Aug 2026 17:34:44 +0000 Subject: [PATCH] Part 7: what readValue(json, Object.class) actually does on a default mapper --- .../H05NeverDeserialiseIntoObject.java | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 src/main/java/com/ankurm/jackson3/part7security/H05NeverDeserialiseIntoObject.java diff --git a/src/main/java/com/ankurm/jackson3/part7security/H05NeverDeserialiseIntoObject.java b/src/main/java/com/ankurm/jackson3/part7security/H05NeverDeserialiseIntoObject.java new file mode 100644 index 0000000..6939cb0 --- /dev/null +++ b/src/main/java/com/ankurm/jackson3/part7security/H05NeverDeserialiseIntoObject.java @@ -0,0 +1,42 @@ +package com.ankurm.jackson3.part7security; + +import tools.jackson.databind.json.JsonMapper; + +/** + * Post: Jackson Security Best Practices — https://ankurm.com/jackson-security-best-practices/ + * Section: "Never Deserialise Untrusted JSON into Object.class" + * + * Worth knowing precisely what readValue(json, Object.class) does on a DEFAULT + * Jackson 3 mapper, because the answer is reassuring and often misunderstood: + * with no default typing active it produces plain Maps, Lists, Strings and numbers. + * The danger only returns when default typing is switched on — as H03 shows. + * + * The rule still stands. Target a specific type; you get validation for free. + */ +public class H05NeverDeserialiseIntoObject { + + public record MyRequestDto(String action, int quantity) { } + + public static void main(String[] args) { + JsonMapper mapper = JsonMapper.builder().build(); + + String untrusted = "{\"action\":\"ship\",\"quantity\":3,\"extra\":{\"nested\":[1,2]}}"; + + Object loose = mapper.readValue(untrusted, Object.class); + System.out.println("as Object : " + loose); + System.out.println("runtime type: " + loose.getClass().getName() + + " <- a plain Map, no arbitrary class was instantiated"); + + MyRequestDto typed = mapper.readValue(untrusted, MyRequestDto.class); + System.out.println("as DTO : " + typed); + + // The real benefit of a specific target type: malformed input fails loudly + // instead of flowing onward as an untyped Map. + try { + mapper.readValue("{\"action\":\"ship\",\"quantity\":\"not-a-number\"}", MyRequestDto.class); + System.out.println("bad input : UNEXPECTEDLY ACCEPTED"); + } catch (Exception e) { + System.out.println("bad input : rejected with " + e.getClass().getSimpleName()); + } + } +}