Corresponds to a property whose type is an array when empty using JsonDeserializer

Overview

There is an API that returns JSON with properties of object type if there is a value and array type if it is empty, and by default the types do not match and cannot be bound to the class.

Caused by: com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize instance of `net.yotama.sample.resource.Resource$Member` out of START_ARRAY token
 at [Source: (String)"{"member": []}"; line: 1, column: 12](through reference chain: net.yotama.sample.resource.Resource["member"])

Use Jackson's JsonDeserializer and replace it with null when it is an array so that it can be bound to the resource class.

Assumed JSON response

If there is a value

{
    "member": {
        "name": "yotama"
    }
}

If there is no value

{
    "member": []
}

environment

Implementation

Resource class

Resource.java


package net.yotama.sample.resource;

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.TreeNode;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import lombok.Value;

import java.io.IOException;

@Value
public class Resource {
    @JsonDeserialize(using = MemberDeserializer.class)
    Member member;

    @Value
    public static class Member {
        String name;
    }

    public static class MemberDeserializer extends JsonDeserializer<Member> {
        private ObjectMapper objectMapper = new ObjectMapper();

        @Override
        public Member deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
            TreeNode treeNode = p.getCodec().readTree(p);
            return treeNode.isObject() ? objectMapper.readValue(treeNode.toString(), Member.class) : null;
        }
    }
}

lombok.config


#I'm using Lombok for simplicity,
# 1.16.Used by Jackson from 20@ConstructorProperties will not be assigned, so set it provisionally
lombok.anyConstructor.addConstructorProperties=true

Operation check

package net.yotama.sample.resource;

import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor;
import org.junit.experimental.theories.DataPoints;
import org.junit.experimental.theories.Theories;
import org.junit.experimental.theories.Theory;
import org.junit.runner.RunWith;

import java.io.IOException;

import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;

@RunWith(Theories.class)
public class ResourceTest {
    private ObjectMapper objectMapper = new ObjectMapper();

    @DataPoints
    public static Fixture[] data = {
            new Fixture("{\"member\": []}", new Resource(null)),
            new Fixture("{\"member\": null}", new Resource(null)),
            new Fixture("{\"member\": {\"name\": \"hoge\"}}", new Resource(new Resource.Member("hoge"))),
    };

    @Theory
    public void test(Fixture fixture) throws IOException {
        assertThat(objectMapper.readValue(fixture.json, Resource.class), is(fixture.expected));
    }

    @RequiredArgsConstructor
    public static class Fixture {
        final String json;
        final Resource expected;
    }
}

Recommended Posts

Corresponds to a property whose type is an array when empty using JsonDeserializer
Null value is entered when assigning to an array
How to output the value when there is an array in the array
Duplicate an object using a generic type
Get the type of an array element to determine if it is an array
[Java] How to convert one element of a String type array to an Int type
I want to issue a connection when a database is created using Spring and MyBatis
How to use an array for a TreeMap key
A memorandum when trying to create a GUI using JavaFX
[Unresolved] An exception occurs when an SSH connection is executed using JSch from a Java 6 application.
A mistake made when displaying an image using Active Storage
How to change a string in an array to a number in Ruby
Initialization with an empty string to an instance of Java String type
How to output array values without using a for statement
Convert an array that may be null to a stream
Monkey patch to return the current time when an empty string is specified in Ruby's Time.parse
When you want to check whether the contents of a property can be converted to a specific type