J'ai l'objet json suivant provenant d'un service distant:

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

Je me débrouille pour le convertir en la classe suivante dépend de la valeur de l'objet à l'intérieur de ce tableau, donc si c'est "name": "MMS" alors la valeur doit être définie sur la valeur de 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
}

Une idée comment faire cela en utilisant l'annotation Jackson ou le désérialiseur personnalisé?

Je peux faire quelque chose comme:

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

Ensuite, faites une itération sur le tableau et définissez chaque propriété, mais vraiment difficile pour la structure de mon projet, je cherche une solution vraiment meilleure dans un but générique (en utilisant 1 méthode pour tous les serveurs distants, et la désérialisation est préférable d'être à l'intérieur de DTO d'une manière ou d'une autre, Je fais une couche d'emballage entre le frontend et le backend).

2
Al-Mothafar 20 avril 2017 à 20:28

3 réponses

Meilleure réponse

Pensez à utiliser la réflexion et nommez les champs BaseDTO en fonction du champ name dans le JSON. L'utilisation d'un constructeur annoté @JsonCreator répond à l'exigence de la "désérialisation mieux pour être à l'intérieur du DTO" . Par exemple.

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) {
            }
        }
    }
}

Et désérialisez comme ceci:

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

Cela initialisera les champs BaseDTO en fonction des éléments du tableau et de leur nom. Il laissera un champ être nul s'il ne peut pas le faire correspondre à un élément de tableau et des exceptions sont levées.

Jackson n'a pas d'annotation pour faire ce que vous voulez AFAIK.

1
Manos Nikolaidis 21 avril 2017 à 13:48

La réponse fournie par @Manos Nikolaidis m'a tellement aidé à coder ma vraie réponse, sa réponse est bonne pour commencer, dans ma cause certaines des valeurs contiennent des espaces ou juste un non-standard, donc je crée une carte à mapper entre champs sur JSON et la classe:

@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) {
        }
    }
}
1
Al-Mothafar 23 avril 2017 à 08:34

J'ai créé ce désérialiseur personnalisé Jackson pour votre classe BaseDTO qui répond à vos besoins. Il recherche la propriété "balance" et quand il l'a trouvée, il sait que les éléments suivants sont "nom" et "unités", donc il les prend. Ensuite, il active la propriété "name" et définit l'AccumulatorDTO actuel dans le champ droit de la classe 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;
}
}

Je l'ai testé avec ce programme simple:

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);
}
}

Et j'ai obtenu la sortie suivante qui semble être correcte, car chaque AccumulatorDTO a été associé à la bonne propriété.

--- 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]]
0
Davis Molinari 22 avril 2017 à 20:13