简体   繁体   中英

Jackson JSON mapping to object with tree traversal

I have the following JSON and I would like to map this to an object.

{
    "response": {
        "original": {
            "status": {
                "code": "SUCCESS"
            },
            "organisationNode": [
                {
                    "organisationId": {
                        "identifier": "2005286047",
                        "identifierType": "BECBE"
                    },
                    "organisationLevel": "Subdivision",
                    "organisationCode": "ENT_CBE",
                    "organisationParentNode": {
                        "organisationId": {
                            "identifier": "0878016185",
                            "identifierType": "BEEUN"
                        },
                        "organisationLevel": "Entity",
                        "organisationCode": "ENT_CBE"
                    }
                }
            ]
        }
    }
}

And I want my Java Object to look something like this:

public class Structure {

  private String status;  //SUCCESS

  private List<OrganisationNode> organisationNodes;

  public class OrganisationNode {
    private String organisationId;  //2005286047
    private String organisationLevel; //Subdivision

    private List<OrganisationNode> organisationNodes;
  }
}

Is there some kind of Jackson annotation to see for example:

@SomeAnnotation("response.original.status.code")
private String status;

I'm calling the JSON service (which supplies me with the JSON response above) with restTemplate like this:

ResponseEntity<Structure> response = restTemplate.postForEntity(endpoint, requestObject, Structure.class);

There is no Jackson annotation that maps an object field to a Json node in the tree but it is not hard to implement. Here are 3 thins that needs to be done:

  1. Create a custom annotation with a path value.
  2. Create a custom deserializer which would locate the node according to the annotation's path value and convert it to a string.
  3. Register an annotation introspector which would tell Jackson that a field or a parameter annotated with your annotation is mapped to a json property and should use your deserializer.

Here is the complete example:

public class JacksonContextualSerializer {

    @Retention(RetentionPolicy.RUNTIME)
    public static @interface SomeAnnotation {
        String value();
    }

    public static final String JSON = "{\n" +
            "    \"response\": {\n" +
            "        \"original\": {\n" +
            "            \"status\": {\n" +
            "                \"code\": \"SUCCESS\"\n" +
            "            }\n" +
            "       }\n" +
            "    }\n" +
            "}";

    public static class PathAwareSerializer extends JsonDeserializer<String>
            implements ContextualDeserializer {
        private String path;

        @Override
        public JsonDeserializer<?> createContextual(DeserializationContext ctxt,
                                                    BeanProperty property)
                throws JsonMappingException {
            // when the serializer implements the ContextualDeserializer, then we have
            // the access to the element's annotation value
            path = property.getMember().getAnnotation(SomeAnnotation.class).value();
            return this;
        }

        @Override
        public String deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException {
            // read JSON as tree
            JsonNode node = jp.readValueAs(JsonNode.class);
            // find the last node starting from the second path element, since the first
            // is covered by the property name (see the introspector)
            String[] pathArray = path.split("\\.");
            for (int i = 1; i < pathArray.length; i++) {
                node = node.get(pathArray[i]);
            }
            return node.asText();
        }
    }

    public static class Bean {
        private final String status;

        @JsonCreator
        public Bean(@SomeAnnotation("response.original.status.code") String status) {
            this.status = status;
        }

        @Override
        public String toString() {
            return "Bean{" +
                    "status='" + status + '\'' +
                    '}';
        }
    }

    public static void main(String[] args) throws IOException {
        ObjectMapper mapper = new ObjectMapper();
        // register an introspector which will inject jackson annotations to elements that are
        // marked by the custom annotation.
        mapper.setAnnotationIntrospector(new JacksonAnnotationIntrospector() {
            @Override
            public PropertyName findNameForDeserialization(Annotated a) {
                if (a.hasAnnotation(SomeAnnotation.class)) {
                    // if annotation is present then this is a property which name is the first
                    // element in the path
                    String path = a.getAnnotation(SomeAnnotation.class).value();
                    return new PropertyName(path.split("\\.")[0]);
                }
                return super.findNameForDeserialization(a);
            }

            @Override
            public Class<? extends JsonDeserializer<?>> findDeserializer(Annotated a) {
                // if the annotation is present, then the property is deserialized using
                // the custom serializer
                if (a.hasAnnotation(SomeAnnotation.class)) {
                    return PathAwareSerializer.class;
                }
                return super.findDeserializer(a);
            }
        });

        System.out.println(mapper.readValue(JSON, Bean.class));
    }
}

Output:

Bean{status='SUCCESS'}

Spring Documentation says that if you have this dependency in your pom.xml and the tag <mvc:annotation-driven> in your spring context, Spring MVC automatically registers a JSON converter for your REST template

<dependency>
   <groupId>org.codehaus.jackson</groupId>
   <artifactId>jackson-mapper-asl</artifactId>
   <version>1.9.3</version>
</dependency>

There are a couple of inconsistencies in your JSON string and Java Object. Eg Status should also be a type with a field code rather than a String

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM