杰克逊如何在课堂上将对象数组转换为平面属性取决于值

时间:2017-04-20 17:28:08

标签: java json jackson

我有来自远程服务的以下json对象:

{
  "accumulators": [
    {
      "balance": "100",
      "name": "SMS",
      "units": "International SMS"
    },
    {
      "balance": "100",
      "name": "VOICE",
      "units": "minutes"
    },
    {
      "balance": "50",
      "name": "MMS",
      "units": "MMS"
    }
  ]
}

我将地图转换为以下类的魔杖取决于该数组中对象的值,因此如果它是"name": "MMS",那么该值必须设置为AccumulatorDTO MMS;的值:

public class BaseDTO {
    private AccumulatorDTO messages;
    private AccumulatorDTO minutes;
    private AccumulatorDTO MMS;

    // setters and geters
}

public class AccumulatorDTO {
    private int balance;
    private String name;
    private String units;

    // setters and geters
}

任何想法如何使用Jackson注释或自定义反序列化器来做到这一点?

我可以做类似的事情:

AccumulatorDTO[] accumulators = (new ObjectMapper()).convertValue(response.asJson().get("accumulators"), AccumulatorDTO[].class);

然后对数组进行迭代并设置每个属性,但是对我的项目结构来说真的很难,我正在寻找一个更好的解决方案来实现通用目的(对所有远程服务器使用1方法,并且更好地在内部进行反序列化) DTO不知何故,我在前端和后端之间做了一些包装层。)

3 个答案:

答案 0 :(得分:1)

考虑使用反射并根据JSON中的BaseDTO字段命名name字段。使用@JsonCreator带注释的构造函数可以更好地满足"反序列化的要求,以某种方式进入DTO" 。 E.g。

class BaseDTO {
    private AccumulatorDTO sms;
    private AccumulatorDTO voice;
    private AccumulatorDTO mms;

    @JsonCreator
    public BaseDTO(@JsonProperty("accumulators") final AccumulatorDTO[] accumulators) {
        for (AccumulatorDTO accumulator : accumulators) {
            String fieldName = accumulator.getName().toLowerCase();
            try {
                Field field = getClass().getDeclaredField(fieldName);
                field.set(this, accumulator);
            } catch (NoSuchFieldException | IllegalAccessException ignored) {
            }
        }
    }
}

并像这样反序列化:

BaseDTO accumulators = new ObjectMapper().readValue(response.asJson(), BaseDTO.class);

这将根据数组元素及其名称初始化BaseDTO字段。如果字段不能与数组元素匹配并且抛出异常,它将使该字段为空。

杰克逊没有注释来做你想要的AFAIK。

答案 1 :(得分:1)

@Manos Nikolaidis提供的答案帮助我编写了我的真实答案,他的答案很好开始,在我的原因中,一些值包含空格或只是非标准,所以我创建了一个地图在JSON和类的字段之间映射:

@JsonCreator
public AccountDTO(@JsonProperty("accumulators") final AccumulatorDTO[] accumulators) {
    HashMap<String, String> accumulatorsMap = new HashMap<>();
    // key is value from JSON, value is field name of class
    accumulatorsMap.put("intl sms", "internationalSMS");
    accumulatorsMap.put("voice", "minutes");
    accumulatorsMap.put("mms", "MMS");
    accumulatorsMap.put("voicemessage", "voiceMessages");
    accumulatorsMap.put("message", "messages");

    for (AccumulatorDTO accumulator : accumulators) {
        String fieldName = accumulator.getName().toLowerCase();
        try {
            Field field = getClass().getDeclaredField(accumulatorsMap.get(fieldName));
            field.set(this, accumulator);
        } catch (NoSuchFieldException | IllegalAccessException ignored) {
        }
    }
}

答案 2 :(得分:0)

我为您的BaseDTO课程创建了这个杰克逊定制解串器,满足您的要求。它寻找“平衡”属性,当它发现它知道以下是“名称”和“单位”所以它需要它们。然后它打开“name”属性,并将当前的AccumulatorDTO设置为BaseDTO类的右侧字段。

public class CustomDeserializer extends StdDeserializer<BaseDTO>{

/**
 * 
 */
private static final long serialVersionUID = 1L;

public CustomDeserializer(Class<BaseDTO> t) {
    super(t);
}

@Override
public BaseDTO deserialize(JsonParser jp, DeserializationContext dc)
                                            throws IOException, JsonProcessingException {
    BaseDTO bd = new BaseDTO();
    JsonToken currentToken = null;
    while ((currentToken = jp.nextValue()) != null) {
        if (jp.getCurrentName() != null && jp.getCurrentName().equals("balance")) 
        {
            System.out.println(jp.getCurrentName());
            AccumulatorDTO adto = new AccumulatorDTO();
            adto.setBalance(Integer.parseInt(jp.getValueAsString()));
            currentToken = jp.nextValue();
            adto.setName(jp.getValueAsString());
            currentToken = jp.nextValue();
            adto.setUnits(jp.getValueAsString());
            switch (adto.getName().toLowerCase())
            {
                case "sms":
                    bd.setMessages(adto);
                    break;
                case "voice":
                    bd.setMinutes(adto);
                    break;
                case "mms":
                    bd.setMMS(adto);
                    break;
            }
        }

    }

    return bd;
}
}

我用这个简单的程序测试了它:

public class JsonArrayExampleApplication {

public static void main(String[] args) {
    //
    String json = "{\"accumulators\": [{\"balance\": \"100\",\"name\": \"SMS\",\"units\": \"International SMS\"" +
    "},{\"balance\": \"100\",\"name\": \"VOICE\",\"units\": \"minutes\"},{\"balance\": \"50\",\"name\": \"MMS\"," +
    "\"units\": \"MMS\"}]}";

    ObjectMapper mapper = new ObjectMapper();

    SimpleModule mod = new SimpleModule("MyModule");
    mod.addDeserializer(BaseDTO.class, new CustomDeserializer(BaseDTO.class));
    mapper.registerModule(mod);

    BaseDTO bdto = null;
    try {
        bdto = mapper.readValue(json, BaseDTO.class);

    }
    catch (IOException e) {
        e.printStackTrace();
    }
    System.out.println("\n--- JSON to JAVA ---\n" + bdto);
}
}

我得到了以下似乎没问题的输出,因为每个AccumulatorDTO都与正确的属性相关联。

--- JSON to JAVA ---
BaseDTO [messages=AccumulatorDTO [balance=100, name=SMS, units=International SMS], minutes=AccumulatorDTO [balance=100, name=VOICE, units=minutes], MMS=AccumulatorDTO [balance=50, name=MMS, units=MMS]]