Como converter um objeto Java (bean) em pares de valor-chave (e vice-versa)?

91

Digamos que eu tenha um objeto java muito simples que tenha apenas algumas propriedades getXXX e setXXX. Este objeto é usado apenas para manipular valores, basicamente um registro ou um mapa de tipo seguro (e desempenho). Freqüentemente, preciso converter este objeto em pares de valores-chave (strings ou tipo seguro) ou converter pares de valores-chave para este objeto.

Além de reflexão ou escrever código manualmente para fazer essa conversão, qual é a melhor maneira de fazer isso?

Um exemplo pode ser o envio deste objeto por jms, sem usar o tipo ObjectMessage (ou converter uma mensagem recebida no tipo certo de objeto).

Shahbaz
fonte
java.beans.Introspector.getBeanInfo(). Ele está integrado ao JDK.
Marquês de Lorne

Respostas:

52

Sempre há beanutils apache commons, mas é claro que ele usa reflexão sob o capô

Maurice Perry
fonte
8
Além disso, não há nada de errado em usar o reflexo, sob o capô. Especialmente se os resultados forem armazenados em cache (para uso futuro), o acesso baseado em reflexão é geralmente rápido o suficiente para a maioria dos casos de uso.
StaxMan
22
Para ser preciso, BeanMap (bean) é a parte específica do beanutils comum que faz o truque.
vdr
3
Desconsiderando as considerações de desempenho, descobri que o uso de BeanMap () em combinação com o ObjectMapper de Jackson da resposta abaixo me deu os melhores resultados. BeanMap conseguiu criar um mapa mais completo do meu objeto e, em seguida, Jackson o converteu na estrutura LinkedHashMap simples, que permitiu modificações mais adiante no pipeline. objectAsMap = objectMapper.convertValue (novo BeanMap (meuObjeto), Map.class);
michaelr524
Não funciona no Android porque usa java.beansdependências severamente limitadas na plataforma. Veja a pergunta relacionada para detalhes.
SqueezyMo
Uma solução que mencione o Apache Commons é na maioria das vezes a melhor solução.
рüффп
177

Muitas soluções potenciais, mas vamos adicionar apenas mais uma. Use Jackson (lib de processamento JSON) para fazer a conversão "json-less", como:

ObjectMapper m = new ObjectMapper();
Map<String,Object> props = m.convertValue(myBean, Map.class);
MyBean anotherBean = m.convertValue(props, MyBean.class);

( esta entrada do blog tem mais alguns exemplos)

Basicamente, você pode converter qualquer tipo compatível: compatível, o que significa que se você converter de tipo para JSON e desse JSON para tipo de resultado, as entradas corresponderiam (se configuradas corretamente, também podem simplesmente ignorar as não reconhecidas).

Funciona bem para casos esperados, incluindo Maps, Lists, arrays, primitivos, POJOs semelhantes a bean.

StaxMan
fonte
2
Esta deve ser a resposta aceita. BeanUtilsé bom, mas não pode lidar com matrizes e enums
Manish Patel
8

A geração de código seria a única outra maneira que consigo pensar. Pessoalmente, eu consegui uma solução de reflexão geralmente reutilizável (a menos que essa parte do código seja absolutamente crítica para o desempenho). Usar JMS parece um exagero (dependência adicional, e nem mesmo é para isso que se destina). Além disso, provavelmente usa reflexão também sob o capô.

Michael Borgwardt
fonte
O desempenho é crítico, então acho que a reflexão está fora de questão. Eu esperava que houvesse algumas ferramentas que usassem asm ou cglib. Comecei a examinar os buffers de protocolo do Google novamente. Não recebi seu comentário sobre o JMS, estou usando-o para comunicar informações entre máquinas.
Shahbaz
Eu não entendi isso. Quanto ao desempenho, há uma diferença entre o desempenho ser importante e aquela parte específica ser crítica para o desempenho. Eu faria um benchmark para ver o quanto isso realmente importa em comparação com o que mais o aplicativo está fazendo.
Michael Borgwardt
Esse também é o meu pensamento. Ou você usa reflexão (direta ou indiretamente) ou tem que gerar código. (Ou escreva você mesmo o código extra, é claro).
extraneon
8

Este é um método para converter um objeto Java em um mapa

public static Map<String, Object> ConvertObjectToMap(Object obj) throws 
    IllegalAccessException, 
    IllegalArgumentException, 
    InvocationTargetException {
        Class<?> pomclass = obj.getClass();
        pomclass = obj.getClass();
        Method[] methods = obj.getClass().getMethods();


        Map<String, Object> map = new HashMap<String, Object>();
        for (Method m : methods) {
           if (m.getName().startsWith("get") && !m.getName().startsWith("getClass")) {
              Object value = (Object) m.invoke(obj);
              map.put(m.getName().substring(3), (Object) value);
           }
        }
    return map;
}

É assim que se chama

   Test test = new Test()
   Map<String, Object> map = ConvertObjectToMap(test);
DeXoN
fonte
1
Não acho que a resposta desejada repete os métodos definidos em cada objeto, soa como reflexão.
Robert Karl,
2
Não sei se seus objetivos são o seu método. Mas eu acho que este é um exemplo excelente quando você deseja programar com tipos genéricos de objetos
DeXoN
5

Provavelmente atrasado para a festa. Você pode usar Jackson e convertê-lo em um objeto Propriedades. Isso é adequado para classes aninhadas e se você quiser a chave para abc = value.

JavaPropsMapper mapper = new JavaPropsMapper();
Properties properties = mapper.writeValueAsProperties(sct);
Map<Object, Object> map = properties;

se você quiser algum sufixo, basta fazer

SerializationConfig config = mapper.getSerializationConfig()
                .withRootName("suffix");
mapper.setConfig(config);

precisa adicionar esta dependência

<dependency>
  <groupId>com.fasterxml.jackson.dataformat</groupId>
  <artifactId>jackson-dataformat-properties</artifactId>
</dependency>
agudeza
fonte
4

Com o Java 8, você pode tentar isso:

public Map<String, Object> toKeyValuePairs(Object instance) {
    return Arrays.stream(Bean.class.getDeclaredMethods())
            .collect(Collectors.toMap(
                    Method::getName,
                    m -> {
                        try {
                            Object result = m.invoke(instance);
                            return result != null ? result : "";
                        } catch (Exception e) {
                            return "";
                        }
                    }));
}
Bax
fonte
3

JSON , por exemplo usando XStream + Jettison, é um formato de texto simples com pares de valores-chave. É suportado, por exemplo, pelo agente de mensagens Apache ActiveMQ JMS para troca de objetos Java com outras plataformas / linguagens.

mjn
fonte
3

Simplesmente usando reflexão e Groovy:

def Map toMap(object) {             
return object?.properties.findAll{ (it.key != 'class') }.collectEntries {
            it.value == null || it.value instanceof Serializable ? [it.key, it.value] : [it.key,   toMap(it.value)]
    }   
}

def toObject(map, obj) {        
    map.each {
        def field = obj.class.getDeclaredField(it.key)
        if (it.value != null) {
            if (field.getType().equals(it.value.class)){
                obj."$it.key" = it.value
            }else if (it.value instanceof Map){
                def objectFieldValue = obj."$it.key"
                def fieldValue = (objectFieldValue == null) ? field.getType().newInstance() : objectFieldValue
                obj."$it.key" = toObject(it.value,fieldValue) 
            }
        }
    }
    return obj;
}
Berardino
fonte
Legal, mas propenso a StackOverflowError
Teo Choong Ping
3

Use o BeanWrapper de juffrou- reflect. É muito performante.

Veja como você pode transformar um bean em um mapa:

public static Map<String, Object> getBeanMap(Object bean) {
    Map<String, Object> beanMap = new HashMap<String, Object>();
    BeanWrapper beanWrapper = new BeanWrapper(BeanWrapperContext.create(bean.getClass()));
    for(String propertyName : beanWrapper.getPropertyNames())
        beanMap.put(propertyName, beanWrapper.getValue(propertyName));
    return beanMap;
}

Eu mesmo desenvolvi o Juffrou. É um código aberto, então você é livre para usá-lo e modificá-lo. E se você tiver alguma dúvida a respeito, terei o maior prazer em responder.

Felicidades

Carlos

Martins
fonte
Eu pensei sobre isso e percebi que minha solução anterior quebra se você tem referências circulares em seu gráfico de bean. Portanto, desenvolvi um método que fará isso de forma transparente e lida perfeitamente com referências circulares. Agora você pode converter um bean em um mapa e vice-versa com juffrou-reflet. Aproveite :)
Martins
3

Ao usar o Spring, também é possível usar o transformador objeto-para-mapa do Spring Integration. Provavelmente não vale a pena adicionar Spring como uma dependência apenas para isso.

Para obter a documentação, pesquise "Object-to-Map Transformer" em http://docs.spring.io/spring-integration/docs/4.0.4.RELEASE/reference/html/messaging-transformation-chapter.html

Essencialmente, ele percorre todo o gráfico do objeto acessível a partir do objeto fornecido como entrada e produz um mapa de todos os campos de tipo / String primitivos nos objetos. Ele pode ser configurado para produzir:

  • um mapa plano: {rootObject.someField = Joe, rootObject.leafObject.someField = Jane} ou
  • um mapa estruturado: {someField = Joe, leafObject = {someField = Jane}}.

Aqui está um exemplo de sua página:

public class Parent{
    private Child child;
    private String name; 
    // setters and getters are omitted
}

public class Child{
   private String name; 
   private List<String> nickNames;
   // setters and getters are omitted
}

O resultado será:

{person.name = George, person.child.name = Jenna, person.child.nickNames [0] = Bimbo. . . etc}

Um transformador reverso também está disponível.

Jan Żankowski
fonte
2

Você pode usar a estrutura Joda:

http://joda.sourceforge.net/

e aproveitar as vantagens do JodaProperties. Isso estipula que você crie beans de uma maneira particular e implemente uma interface específica. No entanto, ele permite que você retorne um mapa de propriedade de uma classe específica, sem reflexão. O código de amostra está aqui:

http://pbin.oogly.co.uk/listings/viewlistingdetail/0e78eb6c76d071b4e22bbcac748c57

Jon
fonte
Parece interessante, mas infelizmente não parece mais ser mantido. Além disso, o mapa de propriedade que ele retorna é um mapa de <String, String>, portanto, não é seguro para tipos.
Shahbaz
1
É verdade que não é mantido desde 2002. Eu só estava curioso para saber se existia uma solução não baseada em reflexão. O mapa de propriedade que ele retorna é na verdade apenas um mapa padrão, sem genéricos como tal ...
Jon
2

Se você não deseja codificar chamadas para cada getter e setter, a reflexão é a única maneira de chamar esses métodos (mas não é difícil).

Você pode refatorar a classe em questão para usar um objeto Properties para conter os dados reais e permitir que cada getter e setter apenas chame get / set nele? Então você tem uma estrutura adequada para o que deseja fazer. Existem até métodos para salvá-los e carregá-los na forma de valor-chave.

Thorbjørn Ravn Andersen
fonte
Não existem métodos, porque depende de você o que é uma chave e o que é valor! Deve ser fácil escrever um serviço que faça essa conversão. Para uma representação simples, o CSV pode ser aceitável.
Martin K.
2

A melhor solução é usar o Dozer. Você só precisa de algo assim no arquivo mapeador:

<mapping map-id="myTestMapping">
  <class-a>org.dozer.vo.map.SomeComplexType</class-a>
  <class-b>java.util.Map</class-b>
</mapping> 

E é isso, Dozer cuida do resto !!!

URL de documentação Dozer


fonte
Por que requer arquivo de mapeamento? Se ele sabe como converter, por que exigir esse trabalho extra - basta pegar o objeto de origem, tipo esperado?
StaxMan
Está bem. É bom saber o motivo, embora eu não tenha certeza se é válido :)
StaxMan 15/10/10
2

É claro que existe o meio mais simples de conversão possível - nenhuma conversão!

em vez de usar variáveis ​​privadas definidas na classe, faça com que a classe contenha apenas um HashMap que armazena os valores da instância.

Em seguida, seus getters e setters retornam e configuram valores para dentro e para fora do HashMap e, quando é hora de convertê-lo em um mapa, voila! - já é um mapa.

Com um pouco de feitiçaria de AOP, você pode até manter a inflexibilidade inerente a um bean permitindo ainda usar getters e setters específicos para cada nome de valor, sem ter que realmente escrever os getters e setters individuais.

Rodney P. Barbati
fonte
2

Você pode usar as propriedades do coletor de filtro de fluxo java 8,

public Map<String, Object> objectToMap(Object obj) {
    return Arrays.stream(YourBean.class.getDeclaredMethods())
            .filter(p -> !p.getName().startsWith("set"))
            .filter(p -> !p.getName().startsWith("getClass"))
            .filter(p -> !p.getName().startsWith("setClass"))
            .collect(Collectors.toMap(
                    d -> d.getName().substring(3),
                    m -> {
                        try {
                            Object result = m.invoke(obj);
                            return result;
                        } catch (Exception e) {
                            return "";
                        }
                    }, (p1, p2) -> p1)
            );
}
Erhanasikoglu
fonte
1

Meu JavaDude Bean Annotation Processor gera código para fazer isso.

http://javadude.googlecode.com

Por exemplo:

@Bean(
  createPropertyMap=true,
  properties={
    @Property(name="name"),
    @Property(name="phone", bound=true),
    @Property(name="friend", type=Person.class, kind=PropertyKind.LIST)
  }
)
public class Person extends PersonGen {}

O código acima gera a superclasse PersonGen que inclui um método createPropertyMap () que gera um Mapa para todas as propriedades definidas usando @Bean.

(Observe que estou mudando ligeiramente a API para a próxima versão - o atributo de anotação será defineCreatePropertyMap = true)

Scott Stanchfield
fonte
Você fez revisões de código? Esta não parece ser uma boa abordagem! Você altera o caminho de herança com anotações! E se o bean já se estende em uma classe ?! Por que você tem que escrever os atributos duas vezes?
Martin K.
Se você já tem uma superclasse, você usa @Bean (superclass = XXX.class, ...) e ele insere a superclasse gerada no meio. Isso tem sido ótimo para revisões de código - muito menos boilerplate potencialmente sujeito a erros para filtrar.
Scott Stanchfield
Não tenho certeza do que você quer dizer com "escrever os atributos duas vezes" - onde eles aparecem duas vezes?
Scott Stanchfield
@Martin K. Se você realmente não quer usar reflexão, mesmo sob o capô, então provavelmente você é forçado a fazer algum tipo de geração de código.
extraneon
1

Você deve escrever um serviço de transformação genérico! Use genéricos para mantê-lo sem digitar (para que você possa converter todos os objetos em chave => valor e vice-versa).

Qual campo deve ser a chave? Obtenha esse campo do bean e acrescente qualquer outro valor não transitório em um mapa de valor.

O caminho de volta é bem fácil. Leia a chave (x) e escreva primeiro a chave e depois cada entrada da lista de volta para um novo objeto.

Você pode obter os nomes das propriedades de um bean com os beanutils do apache commons !

Martin K.
fonte
1

Se você realmente deseja desempenho, pode seguir a rota de geração de código.

Você pode fazer isso sozinho, fazendo sua própria reflexão e construindo um mixin AspectJ ITD.

Ou você pode usar Spring Roo e fazer um complemento Spring Roo . Seu complemento Roo fará algo semelhante ao acima, mas estará disponível para todos que usam Spring Roo e você não precisa usar Anotações de tempo de execução.

Eu fiz ambos. As pessoas fazem uma merda no Spring Roo, mas realmente é a geração de código mais abrangente para Java.

Adam Gent
fonte
1

Uma outra maneira possível é aqui.

O BeanWrapper oferece funcionalidade para definir e obter valores de propriedade (individualmente ou em massa), obter descritores de propriedade e consultar propriedades para determinar se são legíveis ou graváveis.

Company c = new Company();
 BeanWrapper bwComp = BeanWrapperImpl(c);
 bwComp.setPropertyValue("name", "your Company");
Venky
fonte
1

Se se trata de uma árvore de objeto simples para o mapeamento da lista de valores-chave, em que a chave pode ser uma descrição de caminho pontilhada do elemento raiz do objeto para a folha sendo inspecionada, é bastante óbvio que uma conversão de árvore para uma lista de valores-chave é comparável a um mapeamento de objeto para xml. Cada elemento em um documento XML possui uma posição definida e pode ser convertido em um caminho. Portanto, usei o XStream como uma ferramenta de conversão básica e estável e substituí o driver hierárquico e as partes do gravador por uma implementação própria. O XStream também vem com um mecanismo básico de rastreamento de caminho que - sendo combinado com os outros dois - conduz estritamente a uma solução adequada para a tarefa.

Lars Wunderlich
fonte
1

Com a ajuda da biblioteca Jackson, consegui encontrar todas as propriedades da classe do tipo String / inteiro / duplo e respectivos valores em uma classe Map. ( sem usar reflexões api! )

TestClass testObject = new TestClass();
com.fasterxml.jackson.databind.ObjectMapper m = new com.fasterxml.jackson.databind.ObjectMapper();

Map<String,Object> props = m.convertValue(testObject, Map.class);

for(Map.Entry<String, Object> entry : props.entrySet()){
    if(entry.getValue() instanceof String || entry.getValue() instanceof Integer || entry.getValue() instanceof Double){
        System.out.println(entry.getKey() + "-->" + entry.getValue());
    }
}
Amit Kaneria
fonte
0

Usando Gson,

  1. Converter POJO objectem Json
  2. Converter Json em mapa

        retMap = new Gson().fromJson(new Gson().toJson(object), 
                new TypeToken<HashMap<String, Object>>() {}.getType()
        );
Prabs
fonte
0

Podemos usar a biblioteca Jackson para converter um objeto Java em um mapa facilmente.

<dependency>
   <groupId>com.fasterxml.jackson.core</groupId>
   <artifactId>jackson-databind</artifactId>
   <version>2.6.3</version>
</dependency>

Se estiver usando em um projeto Android, você pode adicionar jackson no build.gradle do seu aplicativo da seguinte maneira:

implementation 'com.fasterxml.jackson.core:jackson-core:2.9.8'
implementation 'com.fasterxml.jackson.core:jackson-annotations:2.9.8'
implementation 'com.fasterxml.jackson.core:jackson-databind:2.9.8'

Implementação de amostra

public class Employee {

    private String name;
    private int id;
    private List<String> skillSet;

    // getters setters
}

public class ObjectToMap {

 public static void main(String[] args) {

    ObjectMapper objectMapper = new ObjectMapper();

    Employee emp = new Employee();
    emp.setName("XYZ");
    emp.setId(1011);
    emp.setSkillSet(Arrays.asList("python","java"));

    // object -> Map
    Map<String, Object> map = objectMapper.convertValue(emp, 
    Map.class);
    System.out.println(map);

 }

}

Resultado:

{nome = XYZ, id = 1011, habilidades = [python, java]}

Anubhav
fonte