简体   繁体   中英

Polymorphic deserialization of JSON with jackson with type info from parent node

I have this JSON documents

1:
{
  "type": "first_type",
  "configs": [
    {
      "itemLevel": 1,
      "power": {
        "firstTypeParam": "xxxx"
      }
    },
    {
      "itemLevel": 2,
      "power": {
        "firstTypeParam": "yyy"
      }
    }
  ]
}

2:
{
  "type": "second_type",
  "configs": [
    {
      "itemLevel": 11,
      "power": {
        "anotherParam": true
      }
    },
    {
      "itemLevel": 12,
      "power": {
        "anotherParam": false
    }
  ]
}

A couple of java classes

public class Dto {
  String type;
  Collection<Config>; 
}

public class Config {
  int itemLevel;
  Collection<Power> powers; 
}

public interface Power {}

public class FirstPower implements Power {
  String firstTypeParam;
}

public class SecondPower implements Power {
  boolean anotherParam;
}

I tried to implement custom jackson deserializer @JsonDeserialize(using = MyStdDeserializer.class" on top of Power interface but couldn't find out how to access to neighbor node of the parent with type flag.

Do you know how to fix class hierarchy and/or use jackson features/annotations to deserialize JSON with "first_type" type onto FirstPower class and "second_type" onto SecondPower ?

I'm using jackson 2.9.7 It is possible to change class hierarchy and JSON format little bit and also I have ability to use annotation-based deserialization.

Since the type information is stored in Dto class, the custom JsonDeserializer should be implemented for 'Dto' class instead of 'Power' interface in order to access the type information. The crucial part of the implementation of the custom JsonDeserializer in below code is the line

config.powers.add(parser.readValueAs(getPowerClass(dto.type)));

where getPowerClass method determine the class( FirstPower or SecondPower ) required by using the type of dto . Once the class is known, we can deserialize the power object simply by calling readValueAs method. Following classes(should be put in same package) demonstrate how to implement the custom JsonDeserializer .

Main class

import java.io.IOException;
import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;

public class PolymorphicDeserialize {
    public static void main(String[] args) throws JsonParseException, JsonMappingException, IOException {
        ObjectMapper mapper = new ObjectMapper();

        Dto type1 = mapper.readValue(getType1Json(), Dto.class);
        Dto type2 = mapper.readValue(getType2Json(), Dto.class);

        printDto(type1);
        printDto(type2);
    }

    private static void printDto(Dto dto) {
        System.out.println("type :" + dto.type);
        for (Config config : dto.configs) {
            System.out.println("itemLevel:" + config.itemLevel);
            System.out.println("powers:" + config.powers);
        }
    }

    private static String getType1Json() {

        return "   {                                                       "
                + "        \"type\": \"first_type\",                       "
                + "        \"configs\": [                                  "
                + "          {                                             "
                + "            \"itemLevel\": 1,                           "
                + "            \"power\": {                                "
                + "              \"firstTypeParam\": \"xxxx\"              "
                + "            }                                           "
                + "          },                                            "
                + "          {                                             "
                + "            \"itemLevel\": 2,                           "
                + "            \"power\": {                                "
                + "              \"firstTypeParam\": \"yyy\"               "
                + "            }                                           "
                + "          }                                             "
                + "        ]                                               "
                + "      }                                                 ";

    }

    private static String getType2Json() {

        return "   {                                                       "
                + "        \"type\": \"second_type\",                      "
                + "        \"configs\": [                                  "
                + "          {                                             "
                + "            \"itemLevel\": 11,                          "
                + "            \"power\": {                                "
                + "              \"anotherParam\": true                    "
                + "            }                                           "
                + "          },                                            "
                + "          {                                             "
                + "            \"itemLevel\": 12,                          "
                + "            \"power\": {                                "
                + "              \"anotherParam\": false                   "
                + "            }                                           "
                + "          }                                             "
                + "        ]                                               "
                + "      }                                                 ";

    }
} 

Dto class

import java.util.Collection;

import com.fasterxml.jackson.databind.annotation.JsonDeserialize;

@JsonDeserialize(using = DtoDeserializer.class)
public class Dto {
    String type;
    Collection<Config> configs;
}

DtoDeserializer class

import java.io.IOException;
import java.util.ArrayList;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;

public class DtoDeserializer extends JsonDeserializer<Dto> {

    @Override
    public Dto deserialize(JsonParser parser, DeserializationContext ctxt) throws IOException, JsonProcessingException {
        Dto dto = new Dto();
        dto.configs = new ArrayList<Config>();
        while (parser.nextToken() == JsonToken.FIELD_NAME) {
            deserializeType(parser, dto);
            deserializeConfigs(parser, dto);
        }
        return dto;
    }

    private void deserializeType(JsonParser parser, Dto dto) throws IOException, JsonProcessingException {
        if (!"type".equals(parser.getCurrentName())) {
            return;
        }
        parser.nextToken();
        dto.type = parser.getValueAsString();
    }

    private void deserializeConfigs(JsonParser parser, Dto dto) throws IOException, JsonProcessingException {
        if (!"configs".equals(parser.getCurrentName())) {
            return;
        }
        if (parser.nextToken() != JsonToken.START_ARRAY) {
            return;
        }
        while (parser.nextValue() != null) {
            if (parser.getCurrentToken() != JsonToken.START_OBJECT) {
                continue;
            }
            Config config = new Config();
            config.powers = new ArrayList<Power>();
            while (parser.nextToken() != JsonToken.END_OBJECT) {
                if ("itemLevel".equals(parser.getCurrentName())) {
                    parser.nextToken();
                    config.itemLevel = parser.getValueAsInt();
                } else if ("power".equals(parser.getCurrentName())) {
                    parser.nextToken();
                    config.powers.add(parser.readValueAs(getPowerClass(dto.type)));
                }
            }
            dto.configs.add(config);
        }
    }

    private Class<? extends Power> getPowerClass(String type) {
        if ("first_type".equals(type)) {
            return FirstPower.class;
        } else if ("second_type".equals(type)) {
            return SecondPower.class;
        }
        throw new IllegalArgumentException("Not known type" + type);
    }
}

Power interface

public interface Power {}

FirstPower class

public class FirstPower implements Power {
    String firstTypeParam;

    String getFirstTypeParam() {
        return firstTypeParam;
    }

    void setFirstTypeParam(String firstTypeParam) {
        this.firstTypeParam = firstTypeParam;
    }

    @Override
    public String toString() {
        return "firstTypeParam:" + firstTypeParam;
    }
}

SecondPower class

public class SecondPower implements Power {
    boolean anotherParam;

    boolean isAnotherParam() {
        return anotherParam;
    }

    void setAnotherParam(boolean anotherParam) {
        this.anotherParam = anotherParam;
    }

    @Override
    public String toString() {
        return "anotherParam:" + String.valueOf(anotherParam);
    }
}

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