DATAREDIS-423 - Add support for Jackson2 based HashMapper.
Added FasterXML Jackson `ObjectMapper` based `HashMapper` implementation that allows flattening.
```java
class Person {
String firstname;
String lastname;
Address address;
}
class Address {
String city;
String country;
}
```
```bash
firstname:Jon
lastname:Snow
address:{ city : Castle Black, country : The North }
firstname:Jon
lastname:Snow
address.city:Castle Black
address.country:The North
```
Original pull request: #197.
This commit is contained in:
committed by
Mark Paluch
parent
b1a3559db1
commit
568e41f8dd
@@ -365,6 +365,7 @@ Multiple implementations are available out of the box:
|
||||
|
||||
1. `BeanUtilsHashMapper` using Spring's http://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/beans/BeanUtils.html[BeanUtils]
|
||||
2. `ObjectHashMapper` using <<redis.repositories.mapping>>
|
||||
3. `Jackson2HashMapper` using https://github.com/FasterXML/jackson[FasterXML Jackson].
|
||||
|
||||
[source,java]
|
||||
----
|
||||
|
||||
@@ -0,0 +1,295 @@
|
||||
/*
|
||||
* Copyright 2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.redis.hash;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.data.mapping.model.MappingException;
|
||||
import org.springframework.data.util.DirectFieldAccessFallbackBeanWrapper;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude.Include;
|
||||
import com.fasterxml.jackson.annotation.JsonTypeInfo.As;
|
||||
import com.fasterxml.jackson.core.JsonParseException;
|
||||
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||
import com.fasterxml.jackson.databind.JsonMappingException;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper.DefaultTyping;
|
||||
import com.fasterxml.jackson.databind.SerializationFeature;
|
||||
|
||||
/**
|
||||
* {@link ObjectMapper} based {@link HashMapper} implementation that allows flattening. Given an entity {@code Person}
|
||||
* with an {@code Address} like below the flattening will create individual hash entries for all nested properties.
|
||||
*
|
||||
* <pre>
|
||||
* <code>
|
||||
* class Person {
|
||||
* String firstname;
|
||||
* String lastname;
|
||||
* Address address;
|
||||
* }
|
||||
*
|
||||
* class Address {
|
||||
* String city;
|
||||
* String country;
|
||||
* }
|
||||
* </code>
|
||||
* </pre>
|
||||
*
|
||||
* <pre>
|
||||
* <strong>Normal</strong><br />
|
||||
* <table>
|
||||
* <tr><td>firstname</td><td>Jon<td></tr>
|
||||
* <tr><td>lastname</td><td>Snow<td></tr>
|
||||
* <tr><td>address</td><td>{ city : Castle Black, country : The North }<td></tr>
|
||||
* </table>
|
||||
*
|
||||
* <strong>Flat</strong>: <br />
|
||||
* <table>
|
||||
* <tr><td>firstname</td><td>Jon<td></tr>
|
||||
* <tr><td>lastname</td><td>Snow<td></tr>
|
||||
* <tr><td>address.city</td><td>Castle Black<td></tr>
|
||||
* <tr><td>address.country</td><td>The North<td></tr>
|
||||
* </table>
|
||||
* </pre>
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
* @since 1.8
|
||||
*/
|
||||
public class Jackson2HashMapper implements HashMapper<Object, String, Object> {
|
||||
|
||||
private final ObjectMapper typingMapper;
|
||||
private final ObjectMapper untypedMapper;
|
||||
private final boolean flatten;
|
||||
|
||||
/**
|
||||
* Creates new {@link Jackson2HashMapper} with default {@link ObjectMapper}.
|
||||
*
|
||||
* @param flatten
|
||||
*/
|
||||
public Jackson2HashMapper(boolean flatten) {
|
||||
|
||||
this(new ObjectMapper(), flatten);
|
||||
|
||||
typingMapper.enableDefaultTyping(DefaultTyping.NON_FINAL, As.PROPERTY);
|
||||
typingMapper.configure(SerializationFeature.WRITE_NULL_MAP_VALUES, false);
|
||||
typingMapper.setSerializationInclusion(Include.NON_NULL);
|
||||
typingMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new {@link Jackson2HashMapper}.
|
||||
*
|
||||
* @param mapper must not be {@literal null}.
|
||||
* @param flatten
|
||||
*/
|
||||
public Jackson2HashMapper(ObjectMapper mapper, boolean flatten) {
|
||||
|
||||
Assert.notNull(mapper, "Mapper must not be null!");
|
||||
|
||||
this.typingMapper = mapper;
|
||||
this.flatten = flatten;
|
||||
|
||||
this.untypedMapper = new ObjectMapper();
|
||||
this.untypedMapper.configure(SerializationFeature.WRITE_NULL_MAP_VALUES, false);
|
||||
this.untypedMapper.setSerializationInclusion(Include.NON_NULL);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.redis.hash.HashMapper#toHash(java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public Map<String, Object> toHash(Object source) {
|
||||
|
||||
JsonNode tree = typingMapper.valueToTree(source);
|
||||
return flatten ? flattenMap(tree.fields()) : untypedMapper.convertValue(tree, Map.class);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.redis.hash.HashMapper#fromHash(java.util.Map)
|
||||
*/
|
||||
@Override
|
||||
public Object fromHash(Map<String, Object> hash) {
|
||||
|
||||
try {
|
||||
|
||||
if (flatten) {
|
||||
|
||||
return typingMapper.reader().forType(Object.class)
|
||||
.readValue(untypedMapper.writeValueAsBytes(doUnflatten(hash)));
|
||||
}
|
||||
|
||||
return typingMapper.treeToValue(untypedMapper.valueToTree(hash), Object.class);
|
||||
|
||||
} catch (JsonParseException e) {
|
||||
throw new MappingException(e.getMessage(), e);
|
||||
} catch (JsonMappingException e) {
|
||||
throw new MappingException(e.getMessage(), e);
|
||||
} catch (IOException e) {
|
||||
throw new MappingException(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private Map<String, Object> doUnflatten(Map<String, Object> source) {
|
||||
|
||||
Map<String, Object> result = new LinkedHashMap<String, Object>();
|
||||
Set<String> treatSeperate = new LinkedHashSet<String>();
|
||||
for (Entry<String, Object> entry : source.entrySet()) {
|
||||
|
||||
String key = entry.getKey();
|
||||
String[] args = key.split("\\.");
|
||||
|
||||
if (args.length == 1 && !args[0].contains("[")) {
|
||||
result.put(entry.getKey(), entry.getValue());
|
||||
continue;
|
||||
}
|
||||
|
||||
if (args.length == 1 && args[0].contains("[")) {
|
||||
|
||||
String prunedKey = args[0].substring(0, args[0].indexOf('['));
|
||||
if (result.containsKey(prunedKey)) {
|
||||
appendValueToTypedList(args[0], entry.getValue(), (List<Object>) result.get(prunedKey));
|
||||
} else {
|
||||
result.put(prunedKey, createTypedListWithValue(entry.getValue()));
|
||||
}
|
||||
} else {
|
||||
treatSeperate.add(key.substring(0, key.indexOf('.')));
|
||||
}
|
||||
}
|
||||
|
||||
for (String partial : treatSeperate) {
|
||||
|
||||
Map<String, Object> newSource = new LinkedHashMap<String, Object>();
|
||||
|
||||
for (Entry<String, Object> entry : source.entrySet()) {
|
||||
if (entry.getKey().startsWith(partial)) {
|
||||
newSource.put(entry.getKey().substring(partial.length() + 1), entry.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
if (partial.endsWith("]")) {
|
||||
|
||||
String prunedKey = partial.substring(0, partial.indexOf('['));
|
||||
|
||||
if (result.containsKey(prunedKey)) {
|
||||
appendValueToTypedList(partial, doUnflatten(newSource), (List<Object>) result.get(prunedKey));
|
||||
} else {
|
||||
result.put(prunedKey, createTypedListWithValue(doUnflatten(newSource)));
|
||||
}
|
||||
} else {
|
||||
result.put(partial, doUnflatten(newSource));
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private Map<String, Object> flattenMap(Iterator<Entry<String, JsonNode>> source) {
|
||||
|
||||
Map<String, Object> resultMap = new HashMap<String, Object>();
|
||||
this.doFlatten("", source, resultMap);
|
||||
return resultMap;
|
||||
}
|
||||
|
||||
private void doFlatten(String propertyPrefix, Iterator<Entry<String, JsonNode>> inputMap,
|
||||
Map<String, Object> resultMap) {
|
||||
|
||||
if (StringUtils.hasText(propertyPrefix)) {
|
||||
propertyPrefix = propertyPrefix + ".";
|
||||
}
|
||||
|
||||
while (inputMap.hasNext()) {
|
||||
|
||||
Entry<String, JsonNode> entry = inputMap.next();
|
||||
flattenElement(propertyPrefix + entry.getKey(), entry.getValue(), resultMap);
|
||||
}
|
||||
}
|
||||
|
||||
private void flattenElement(String propertyPrefix, Object source, Map<String, Object> resultMap) {
|
||||
|
||||
if (!(source instanceof JsonNode)) {
|
||||
|
||||
resultMap.put(propertyPrefix, source);
|
||||
return;
|
||||
}
|
||||
|
||||
JsonNode element = (JsonNode) source;
|
||||
if (element.isArray()) {
|
||||
|
||||
Iterator<JsonNode> nodes = element.elements();
|
||||
|
||||
while (nodes.hasNext()) {
|
||||
|
||||
JsonNode cur = nodes.next();
|
||||
if (cur.isArray()) {
|
||||
this.falttenCollection(propertyPrefix, cur.elements(), resultMap);
|
||||
}
|
||||
}
|
||||
|
||||
} else if (element.isContainerNode()) {
|
||||
this.doFlatten(propertyPrefix, element.fields(), resultMap);
|
||||
} else {
|
||||
resultMap.put(propertyPrefix, new DirectFieldAccessFallbackBeanWrapper(element).getPropertyValue("_value"));
|
||||
}
|
||||
}
|
||||
|
||||
private void falttenCollection(String propertyPrefix, Iterator<JsonNode> list, Map<String, Object> resultMap) {
|
||||
|
||||
int counter = 0;
|
||||
while (list.hasNext()) {
|
||||
JsonNode element = list.next();
|
||||
flattenElement(propertyPrefix + "[" + counter + "]", element, resultMap);
|
||||
counter++;
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private void appendValueToTypedList(String key, Object value, List<Object> destination) {
|
||||
|
||||
int index = Integer.valueOf(key.substring(key.indexOf('[') + 1, key.length() - 1)).intValue();
|
||||
List<Object> resultList = ((List<Object>) destination.get(1));
|
||||
if (resultList.size() < index) {
|
||||
resultList.add(value);
|
||||
} else {
|
||||
resultList.add(index, value);
|
||||
}
|
||||
}
|
||||
|
||||
private List<Object> createTypedListWithValue(Object value) {
|
||||
|
||||
List<Object> listWithTypeHint = new ArrayList<Object>();
|
||||
listWithTypeHint.add(ArrayList.class.getName()); // why jackson? why?
|
||||
List<Object> values = new ArrayList<Object>();
|
||||
values.add(value);
|
||||
listWithTypeHint.add(values);
|
||||
return listWithTypeHint;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
* Copyright 2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.redis.mapping;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
|
||||
import org.hamcrest.core.Is;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.Parameterized;
|
||||
import org.junit.runners.Parameterized.Parameters;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.data.redis.Address;
|
||||
import org.springframework.data.redis.ConnectionFactoryTracker;
|
||||
import org.springframework.data.redis.Person;
|
||||
import org.springframework.data.redis.connection.RedisConnectionFactory;
|
||||
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
|
||||
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.data.redis.hash.Jackson2HashMapper;
|
||||
|
||||
/**
|
||||
* Integration tests for {@link Jackson2HashMapper}.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
@RunWith(Parameterized.class)
|
||||
public class Jackson2HashMapperTests {
|
||||
|
||||
RedisTemplate<String, Object> template;
|
||||
RedisConnectionFactory factory;
|
||||
Jackson2HashMapper mapper;
|
||||
|
||||
public Jackson2HashMapperTests(RedisConnectionFactory factory) throws Exception {
|
||||
|
||||
this.factory = factory;
|
||||
if (factory instanceof InitializingBean) {
|
||||
((InitializingBean) factory).afterPropertiesSet();
|
||||
}
|
||||
|
||||
ConnectionFactoryTracker.add(factory);
|
||||
}
|
||||
|
||||
@Parameters
|
||||
public static Collection<RedisConnectionFactory> params() {
|
||||
return Arrays.<RedisConnectionFactory> asList(new JedisConnectionFactory(), new LettuceConnectionFactory());
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void cleanUp() {
|
||||
ConnectionFactoryTracker.cleanUp();
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
|
||||
this.template = new RedisTemplate<String, Object>();
|
||||
this.template.setConnectionFactory(factory);
|
||||
template.afterPropertiesSet();
|
||||
|
||||
this.mapper = new Jackson2HashMapper(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREDIS-423
|
||||
*/
|
||||
@Test
|
||||
public void shouldWriteReadHashCorrtectly() {
|
||||
|
||||
Person jon = new Person("jon", "snow", 19);
|
||||
Address adr = new Address();
|
||||
adr.setStreet("the wall");
|
||||
adr.setNumber(100);
|
||||
jon.setAddress(adr);
|
||||
|
||||
template.opsForHash().putAll("JON-SNOW", mapper.toHash(jon));
|
||||
|
||||
Person result = (Person) mapper.fromHash(template.<String, Object> opsForHash().entries("JON-SNOW"));
|
||||
Assert.assertThat(result, Is.is(jon));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,204 @@
|
||||
/*
|
||||
* Copyright 2016 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.redis.mapping;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.Parameterized;
|
||||
import org.junit.runners.Parameterized.Parameters;
|
||||
import org.springframework.data.redis.Address;
|
||||
import org.springframework.data.redis.Person;
|
||||
import org.springframework.data.redis.hash.HashMapper;
|
||||
import org.springframework.data.redis.hash.Jackson2HashMapper;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link Jackson2HashMapper}.
|
||||
*
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
@RunWith(Parameterized.class)
|
||||
public class Jackson2HashMapperUnitTests extends AbstractHashMapperTest {
|
||||
|
||||
private final Jackson2HashMapper mapper;
|
||||
|
||||
public Jackson2HashMapperUnitTests(Jackson2HashMapper mapper) {
|
||||
this.mapper = mapper;
|
||||
}
|
||||
|
||||
@Parameters
|
||||
public static Collection<Jackson2HashMapper> params() {
|
||||
return Arrays.asList(new Jackson2HashMapper(true), new Jackson2HashMapper(false));
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("rawtypes")
|
||||
protected <T> HashMapper mapperFor(Class<T> t) {
|
||||
return this.mapper;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREDIS-423
|
||||
*/
|
||||
@Test
|
||||
public void shouldMapTypedListOfSimpleType() {
|
||||
|
||||
WithList source = new WithList();
|
||||
source.strings = Arrays.asList("spring", "data", "redis");
|
||||
assertBackAndForwardMapping(source);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREDIS-423
|
||||
*/
|
||||
@Test
|
||||
public void shouldMapTypledListOfComplexType() {
|
||||
|
||||
WithList source = new WithList();
|
||||
|
||||
source.persons = Arrays.asList(new Person("jon", "snow", 19), new Person("tyrion", "lannister", 27));
|
||||
assertBackAndForwardMapping(source);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREDIS-423
|
||||
*/
|
||||
@Test
|
||||
public void shouldMapTypedListOfComplexObjectWihtNestedElements() {
|
||||
|
||||
WithList source = new WithList();
|
||||
|
||||
Person jon = new Person("jon", "snow", 19);
|
||||
Address adr = new Address();
|
||||
adr.setStreet("the wall");
|
||||
adr.setNumber(100);
|
||||
jon.setAddress(adr);
|
||||
|
||||
source.persons = Arrays.asList(jon, new Person("tyrion", "lannister", 27));
|
||||
assertBackAndForwardMapping(source);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREDIS-423
|
||||
*/
|
||||
@Test
|
||||
public void shouldMapNestedObject() {
|
||||
|
||||
Person jon = new Person("jon", "snow", 19);
|
||||
Address adr = new Address();
|
||||
adr.setStreet("the wall");
|
||||
adr.setNumber(100);
|
||||
jon.setAddress(adr);
|
||||
|
||||
assertBackAndForwardMapping(jon);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREDIS-423
|
||||
*/
|
||||
@Test
|
||||
public void shouldMapUntypedList() {
|
||||
|
||||
WithList source = new WithList();
|
||||
source.objects = Arrays.<Object> asList(Integer.valueOf(100), "foo", new Person("jon", "snow", 19));
|
||||
assertBackAndForwardMapping(source);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREDIS-423
|
||||
*/
|
||||
@Test
|
||||
public void shouldMapTypedMapOfSimpleTypes() {
|
||||
|
||||
WithMap source = new WithMap();
|
||||
source.strings = new LinkedHashMap<String, String>();
|
||||
source.strings.put("1", "spring");
|
||||
source.strings.put("2", "data");
|
||||
source.strings.put("3", "redis");
|
||||
assertBackAndForwardMapping(source);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREDIS-423
|
||||
*/
|
||||
@Test
|
||||
public void shouldMapTypedMapOfComplexTypes() {
|
||||
|
||||
WithMap source = new WithMap();
|
||||
source.persons = new LinkedHashMap<String, Person>();
|
||||
source.persons.put("1", new Person("jon", "snow", 19));
|
||||
source.persons.put("2", new Person("tyrion", "lannister", 19));
|
||||
assertBackAndForwardMapping(source);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREDIS-423
|
||||
*/
|
||||
@Test
|
||||
public void shouldMapUntypedMap() {
|
||||
|
||||
WithMap source = new WithMap();
|
||||
source.objects = new LinkedHashMap<String, Object>();
|
||||
source.objects.put("1", "spring");
|
||||
source.objects.put("2", Integer.valueOf(100));
|
||||
source.objects.put("3", "redis");
|
||||
assertBackAndForwardMapping(source);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREDIS-423
|
||||
*/
|
||||
@Test
|
||||
public void nestedStuff() {
|
||||
|
||||
WithList nestedList = new WithList();
|
||||
nestedList.objects = new ArrayList<Object>();
|
||||
|
||||
WithMap deepNestedMap = new WithMap();
|
||||
deepNestedMap.persons = new LinkedHashMap<String, Person>();
|
||||
deepNestedMap.persons.put("jon", new Person("jon", "snow", 24));
|
||||
|
||||
nestedList.objects.add(deepNestedMap);
|
||||
|
||||
WithMap outer = new WithMap();
|
||||
outer.objects = new LinkedHashMap<String, Object>();
|
||||
outer.objects.put("1", nestedList);
|
||||
|
||||
assertBackAndForwardMapping(outer);
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class WithList {
|
||||
List<String> strings;
|
||||
List<Object> objects;
|
||||
List<Person> persons;
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class WithMap {
|
||||
Map<String, String> strings;
|
||||
Map<String, Object> objects;
|
||||
Map<String, Person> persons;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user