Как конвертировать карту чье значение json в POJO - PullRequest
0 голосов
/ 06 февраля 2020

Например, наш тип цели Info со следующим определением

class Info {
  Person person;
  // a dozen of other properties
  // getters and setters
}

class Person {
  String name;
  int age;

  // getters and setters
}

, а наша Карта похожа на Collections.singletonMap("person", "{\"name\": \"nick\", \"age\": 18}")

Как я могу преобразовать эту Карту в Info объект? (добавление конструктора для Person или анализ значения по одному недопустимо, потому что на самом деле существует дюжина свойств, таких как Person.)

Пробовал использовать Джексона с objectMapper.convertValue, но выдает исключение с сообщением no String-argument constructor/factory method to deserialize from String value

Ответы [ 4 ]

1 голос
/ 06 февраля 2020

Если у вас действительно есть Map<String, String>, а ключом является имя поля, а значением является структура json, другого шанса нет, кроме:

switch (map.getKey()) { 
    case "person": 
           Person person = mapper.readValue(map.getValue(), Person.class);
           info.setPerson(person);
           break;
    //TODO add your other Map-Keys here
}

Но если у вас есть нормальный json структура типа:

{
   "person": {
      "name": "nick",
      "age": 18
   }
}

Тогда вы можете просто:

Info info = mapper.readValue(json, Info.class);
1 голос
/ 06 февраля 2020

Джексона ObjectMapper знает, как преобразовать Map в POJO, но он не может отобразить строку с текстом JSON в POJO, поэтому сначала нужно проанализировать текст JSON в нечто общее c (Map или JsonNode дерево).

По сути, конвертируйте ваши Map<String, String> в Map<String, JsonNode>.

Map<String, String> data = Collections.singletonMap("person", "{\"name\": \"nick\", \"age\": 18}");

ObjectMapper mapper = new ObjectMapper();
Map<String, Object> dataTree = new HashMap<>();
for (Entry<String, String> entry : data.entrySet())
    dataTree.put(entry.getKey(), mapper.readTree(entry.getValue()));
Info info = mapper.convertValue(dataTree, Info.class);

System.out.println("name = " + info.getPerson().getName());
System.out.println("age = " + info.getPerson().getAge());

Вывод

name = nick
age = 18
0 голосов
/ 24 февраля 2020

Хорошо, после просмотра исходного кода я нашел решение:

Сначала настройте BeanDeserialzier

class StringBeanDeserializer extends BeanDeserializer {
    private ObjectMapper objectMapper;
    public StringBeanDeserializer(BeanDeserializerBuilder builder, BeanDescription beanDesc, BeanPropertyMap properties, Map<String, SettableBeanProperty> backRefs, HashSet<String> ignorableProps, boolean ignoreAllUnknown, boolean hasViews, ObjectMapper objectMapper) {
        super(builder, beanDesc, properties, backRefs, ignorableProps, ignoreAllUnknown, hasViews);
        this.objectMapper = objectMapper == null ? new ObjectMapper() : objectMapper;
    }

    @Override
    public Object deserializeFromString(JsonParser p, DeserializationContext ctxt) throws IOException {
        if (this._beanType.isTypeOrSubTypeOf(CharSequence.class)) {
            return super.deserializeFromString(p, ctxt);
        }
        return objectMapper.readValue(p.getText(), this._beanType.getRawClass());
    }
}

Затем напишите Builder для создания этого BeanDeserialzier

class StringBeanDeserializerBuilder extends BeanDeserializerBuilder {
    private ObjectMapper objectMapper;
    public StringBeanDeserializerBuilder(BeanDescription beanDesc, DeserializationContext ctxt) {
        super(beanDesc, ctxt);
    }

    public StringBeanDeserializerBuilder(BeanDescription beanDesc, DeserializationContext ctxt, ObjectMapper objectMapper) {
        this(beanDesc, ctxt);
        this.objectMapper = objectMapper;
    }

    protected StringBeanDeserializerBuilder(BeanDeserializerBuilder src) {
        super(src);
    }

    /**
     * Method for constructing a {@link BeanDeserializer}, given all
     * information collected.
     */
    public JsonDeserializer<?> build()
    {
        Collection<SettableBeanProperty> props = _properties.values();
        _fixAccess(props);
        BeanPropertyMap propertyMap = BeanPropertyMap.construct(props,
                _config.isEnabled(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES),
                _collectAliases(props));
        propertyMap.assignIndexes();

        // view processing must be enabled if:
        // (a) fields are not included by default (when deserializing with view), OR
        // (b) one of properties has view(s) to included in defined
        boolean anyViews = !_config.isEnabled(MapperFeature.DEFAULT_VIEW_INCLUSION);
        if (!anyViews) {
            for (SettableBeanProperty prop : props) {
                if (prop.hasViews()) {
                    anyViews = true;
                    break;
                }
            }
        }

        // one more thing: may need to create virtual ObjectId property:
        if (_objectIdReader != null) {
            /* 18-Nov-2012, tatu: May or may not have annotations for id property;
             *   but no easy access. But hard to see id property being optional,
             *   so let's consider required at this point.
             */
            ObjectIdValueProperty prop = new ObjectIdValueProperty(_objectIdReader, PropertyMetadata.STD_REQUIRED);
            propertyMap = propertyMap.withProperty(prop);
        }

        return new StringBeanDeserializer(this,
                _beanDesc, propertyMap, _backRefProperties, _ignorableProps, _ignoreAllUnknown,
                anyViews, objectMapper);
    }
}

Наконец, напишите фабрику, которая использует этот Builder

public class StringBeanDeserializeFactory extends BeanDeserializerFactory {
    private ObjectMapper objectMapper = new ObjectMapper();

    /**
     * Globally shareable thread-safe instance which has no additional custom deserializers
     * registered
     */
    public final static BeanDeserializerFactory instance = new StringBeanDeserializeFactory(
            new DeserializerFactoryConfig());

    public StringBeanDeserializeFactory(DeserializerFactoryConfig config) {
        super(config);
    }

    /**
     * Overridable method that constructs a {@link BeanDeserializerBuilder}
     * which is used to accumulate information needed to create deserializer
     * instance.
     */
    protected BeanDeserializerBuilder constructBeanDeserializerBuilder(DeserializationContext ctxt,
                                                                       BeanDescription beanDesc) {
        return new StringBeanDeserializerBuilder(beanDesc, ctxt, objectMapper);
    }
}

Пример использования:

        ObjectMapper objectMapper = new ObjectMapper(null, null, new DefaultDeserializationContext.Impl(StringBeanDeserializeFactory.instance));
        Map<String, String> map = ImmutableMap.of("person", objectMapper.writeValueAsString(new Person("nick", 25)),
                "raw", "test");
        System.out.println(objectMapper.convertValue(map, Data.class));
0 голосов
/ 06 февраля 2020

Этого легко добиться с помощью библиотеки Gson, созданной Google для обработки структур json.

Map<String, String> data = Collections.singletonMap("person", "{\"name\": \"nick\", \"age\": 18}");
Person person = new Gson.fromJson(data.get("person"), Person.class);
Info info = new Info();
info.setPerson(person);
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...