Let's say I have simple HashMap:
Map<String, String> map = new HashMap<>();
map.put("field1","value1");
map.put("field2", "value2");
I also have simple java class:
class SimpleClass {
public String field1;
public String field2;
}
What is simplest and most elegant way to create SimpleClass instance with corresponding fields/values taken from map? In this case, resulting SimpleClass instance should get field1 value 'value1andfield2valuevalue2`.
SimpleClass is already defined, now we need to find matching keys in map, if match found, it's value should be assigned to corresponding class field.
In my real application, I will get list of maps and I need to transform it into List<SimpleClass>. Map can contain additional keys, that need to be ommited (if no matching class field is available).
Can I use (for example) Guava to make transoformation like this? I'm on Android so java streams can't be used so far.
[edit]
My attempt:
private SimpleClass mapToObject(Map<String, String> map)
{
SimpleClass result = new SimpleClass();
for(Field f: result.getClass().getDeclaredFields())
{
try
{
f.setAccessible(true);
f.set(result,map.get(f.getName()));
}
catch (Exception e)
{
Log.d("error", e.toString());
}
}
return result;
}
Use ObjectMapper ,
SimpleClass simpleClass = objectMapper.convertValue(map, SimpleClass.class);
Related
I have a line of code:
private final Map<MyClassA<?>, MyClassB<?>> myMap = new HashMap<>();
Is there any way to define that map in a way that would tell the compiler that the ? in each case must be the same class?
Something like this?
private final <T> Map<MyClassA<T>, MyClassB<T>> myMap = new HashMap<>();
... which is not legal syntax?
It's just a self-learning question at this point.
FWIW, I want to add a method
public <T> MyClassB<T> getForA(MyClassA<T> a) {
return this.myMap.get(a);
}
But I get a compile error unless I can define myMap to insist that both the key and the value wrap the same type.
As you already figured out, you can't do that if key and value are different for different entries:
map.put(new MyClassA<Foo>(), new MyClassB<Foo>());
map.put(new MyClassA<Bar>(), new MyClassB<Bar>());
(I've taken this requirement from your comment)
What you can do is to write some helper methods, which enforce this constraint:
public <T> void put(MyClassA<T> key, MyClass<B> value) {
// Maybe check at runtime if the constraint is not validated?
map.put(key, value);
}
public <T> MyClassB<T> get(MyClassA<T> key) {
// This will produce an unchecked warning.
return (T) map.get(key);
}
As long as you only access the map through such helper methods (and don't use raw types), the constraint on the map will not be violated, which allows you to write type safe code.
The only part that is not typesafe are those helper methods, and that's where you have to be careful.
You can do something similar if you introduce one static inner class for the type you need. For example:
public class DoubleGenericTest<T> {
public static class MapHolder<Z> {
private final Map<MyClassA<Z>, MyClassB<Z>> myMap = new HashMap<>();
}
private final MapHolder<String> stringMap = new MapHolder<>();
private final MapHolder<Integer> integerMap = new MapHolder<>();
}
class MyClassA<X> {}
class MyClassB<Y> {}
This gives you the class you need to hang the type parameter onto. Maybe not ideal in every situation but it's the only thing I can think of.
I want to migrate my code from GSon to MOSHI in order to get the benefits of the common underlying usage of the OK-Libraries as I am using this also with OKHTTP and Retrofit.
But a task that is simple with Gson seems to be complicated with MOSHI:
I have a class that contains a list of objects.
And these objects consist of fieldname/value pairs - I implemeted that as a HashMap. In this class there are some more constructors and methods, but for JSON only the field/value-pairs are relevant.
Stripped down to the bare minimum, my JSON should look like:
{"children":[{"f1":"v11","f2":"v12"},{"f1":"v21","f2":"v22"}]}
When I try to convert these classes to JSON with MOSHI and back, the children are empty.
Conversion to JSON gives
{"children":[{},{}]}
And deserialisation of the json-string from above to Class2 gives 2 children, but the children are emppty.
In my real code I that parent-object also contains lists of objects of other classes - those classes work as expected. The problem here seems to be that my child-class extends from HashMap.
With Gson everything works as expected.
Here is the Unit-Test, i wrote to test the behavior.
public class Test_Moshi {
private final Moshi moshi = new Moshi.Builder().build();
private static class Class1 extends HashMap<String, Object> {
//Some Constructors and methods omitted for the test.
//Relevant for the serilisation to JSON are only the keys and values in the map.
}
private static class Class2 {
List<Class1> children = new ArrayList<>();
}
#Test public void test1() {
Class1 child;
Class2 parent = new Class2();
child = new Class1();
child.put("f1", "v11");
child.put("f2", "v12");
parent.children.add(child);
child = new Class1();
child.put("f1", "v21");
child.put("f2", "v22");
parent.children.add(child);
String json_gson = new Gson().toJson(parent);
String json_moshi = moshi.adapter(Class2.class).toJson(parent);
assertEquals(json_gson, json_moshi);
}
#Test public void test2() throws IOException {
String json = "{\"children\":[{\"f1\":\"v11\",\"f2\":\"v12\"},{\"f1\":\"v21\",\"f2\":\"v22\"}]}";
Class2 class2 = moshi.adapter(Class2.class).fromJson(json);
assertEquals(2, class2.children.size());
assertEquals("Child 1 contains expected number of fields", 2, class2.children.get(0).size());
assertEquals("Child 2 contains expected number of fields", 2, class2.children.get(1).size());
}
}
After some sleep I found a solution (although I think that Moshi should handle this case out of the box):
As you can read here in the answers, Moshi correctly handles the Map<> interface. The solution is to provide Custom Type Adapter that maps the class to the Map-Interface and back. The rest is then handled by Moshi.
The Code from my question has to be changed as follows:
Create an adapter-class that maps to the Map-Interface as described in the documentation of Moshi.
private static class Class1 extends HashMap<String, Object> {
public static class class1ToJsonAdapter {
#ToJson
public Map<String, Object> toJson(Class1 dat) {
return (Map<String,Object>)dat;
}
#FromJson
public Class1 fromJson(Map<String,Object> json) {
Class1 result = new Class1();
for (String key : json.keySet())
result.put(key, json.get(key));
return result;
}
}
//Some Constructors and methods omitted for the test.
//Relevant for the serilisation to JSON are only the keys and values in the map.
}
and this adapter has to be added to the moshi-object
private final Moshi moshi = new Moshi.Builder()
.add(new Class1.class1ToJsonAdapter())
.build();
Now the conversion from and to JSON works as expected.
I have a complex object and for some of the nested objects I need to serialize them into JSON fields instead of JSON objects.
Eg.
public class Outer {
private String someField;
private AnotherClass anotherField;
}
public class AnotherClass {
#XmlElement(name = "useThisName")
private String someField;
private String anotherField;
}
How can I make a custom serializer that will be for the nested object and obey the annotations so the fields are named properly?
My use case for this is to use the ObjectMapper.convertValue() method to create a Map so that I can loop through it and create NameValuePairs for a rest url.
In the end I am hoping to end up with a
Map<String, String>
That I can loop over and create apache BasicNameValuePairs from.
Below is some code I want to use for the end result if I can get everything to serialize properly.
Map<String, String> parameters
= DefaultJacksonMapper.getDefaultJacksonMapper().convertValue(obj, LinkedHashMap.class);
return parameters
.entrySet()
.stream()
.map(entry -> new BasicNameValuePair(entry.getKey(), entry.getValue()))
.collect(Collectors.toList());
If I convert this to a map now my output is like:
"someField" -> "data"
"anotherField" -> "size = 2"
I am trying to get the Map to have the following output which I feel like I need a custom serializer.
"someField" -> "data"
"useThisName" -> "data"
"anotherField" -> "data"
Ok I figured this out.
I ended up creating a new Module that inherited off of SimpleModule. Then I created a new Abstract class like
public abstract class OuterMixin {
#JsonUnwrapped
private AnotherClass anotherField;
}
I also had to annotate the AnotherClass with JsonProperty Like:
public class AnotherClass {
#XmlElement(name = "useThisName")
#JsonProperty("useThisName")
private String someField;
private String anotherField;
}
The when I got my Object Mapper I just registered my module with it and did the conversion and it all worked out.
As a side note I have another property that I had to write a custom serializer for and the #JsonUnwrapped did not work with that.
The title may be a bit hard to understand, but let me just briefly describe my problem.
Let's assume I have an annotation like this:
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.FIELD)
public #interface Identifier {
}
Now, I make a class which annotates any of its fields with it:
public class Student {
private String name;
private String surname;
#Identifier
private String idNumber;
...
}
Finally, at runtime I want to create a Map with the key type of typeof(field annotated with #Identifier) and the value type of Student. Note that any field can be annotated with #Identifier.
Any ideas?
EDIT
Ok, let me clarify this a bit:
class Student {
private String name;
private String surname;
#Identifier
private String idNumber;
}
class Foo {
#Identifier
private Integer x;
}
// Now, what I want to have are two maps:
SortedMap students; // key type: String
// value type: Student
SortedMap foos; // key type: Integer
// value type: Foo
Thanks in advance!
I'm still not exactly sure what you want to do.
at runtime I want to create a Map with the key type of typeof(field
annotated with #Identifier) and the value type of Student
You can create a raw Map or a Map<Object, Object>. You can get the type of the field annotated with #Identifier. I'm not sure what you mean by value type of Student so I'll assume you mean the type Student, ie. its Class object.
public static void main(String[] args) throws Exception {
Class<?> clazz = Student.class;
Map<Object, Object> map = new HashMap<>();
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
Identifier annotation = field.getAnnotation(Identifier.class);
if (annotation != null) {
map.put(field.getType(), clazz);
}
}
System.out.println(map);
}
With your example class in your question, this prints
{class java.lang.String=class com.spring.Student}
So the annotated field type is mapped to the class type.
You won't be able to have a Map<String,Student> though because you don't know the type String (and possibly not even Student) at compile time. You can try casting, but you're setting yourself up for a number of ClassCastExceptions.
So you are going to have a method (myMethod in my example) which will be passed objects which may hold a field annotated with #Identifier.
Sorry to burst your bubble but there is no way to keep generic information at runtime. The closest you can get is having a Map<Field, Class<?>> which holds key-value pairs with your desired type. This is how you do it:
public Map<Field, Class<?>> myMethod(Object obj) {
Map<Field, Class<?>> result = new HashMap<Field, Class<?>>();
for(Field field : obj.getClass().getDeclaredFields()) {
Identifier identifier = field.getAnnotation(Identifier.class);
if(identifier != null) {
result.put(field, obj.getClass());
return result;
}
}
return result;
}
In my example the result will either be an empty Map or a Map with one key-value pair. I suggest you should use a separate type for the result instead of a Map. Of course you can tamper with the code if you want something other than a Field for example you can use Field's getType() or getGenericType() methods.
I have a class, lets call it Fruit, and I have a HashMap. I want to be able to initialize a new instance of Fruit, but set to the values in HashMap. So for example:
Map<String, String> map = new HashMap<String, String>();
map.put("name", "Banana");
map.put("color", "Yellow");
Then I want to be initialize a new Fruit instance like so:
Fruit myFruit = new Fruit(map);
or
Fruit myFruit = (Fruit)map;
Is this possible in Java, by means of iterating the Map?
The second is not possible because a HashMap is not a Fruit. You could do the first by providing a constructor that takes a Map<String, String> argument.
public Fruit(Map<String, String> map) {
this.name = map.get("name");
this.color = map.get("color");
}
It seems like you can use reflection for this
Fruit f = new Fruit();
Class aClass = f.getClass();
for(Field field : aClass.getFields()){
if(map.containsKey(field.getName())){
field.set(f,map.get(field.getName()));
}
}
Little old but:
import com.fasterxml.jackson.databind.ObjectMapper;
...
final ObjectMapper mapper = new ObjectMapper();
final Map myObjectMapped = new HashMap();
//fill map
final Class clazz = Class.forName(MyClassToBeConverted.class.getName());
final MyClassToBeConverted convertedObj = (MyClassToBeConverted) mapper.convertValue(myObjectMapped, clazz);
...
Yes, it's possible. But you'd have to write a constructor for Fruit that knows how to pull values -- and which values -- from the map.
public Fruit(Map params) {
this.setColor(map.get("color"));
this.setName(map.get("name"));
}
I have fixed Anni's solution, now it supports inheritance, and static and final fields.
By the way, I have not checked for type mismatches.
public static void populateBean(Object bean, Map<String, Object> properties) throws Exception {
Class<?> clazz = bean.getClass();
while(clazz != null) {
for (Field field : clazz.getDeclaredFields()) {
int modifiers = field.getModifiers();
if (!Modifier.isStatic(modifier) && !Modifier.isFinal(modifier)) {
if (map.containsKey(field.getName())) {
field.accessible(true);
field.set(bean, map.get(field.getName()));
}
}
}
clazz = clazz.getSuperclass();
}
}
By the way Apache BeanUtils DynaBeans almost does what you want, as far as I remember it supports Java Beans Introspection.
Maybe it could be a little slower in comparison to other solutions, but for not demanding purposes, my code works very well for me (And it is very simple and clean):
public class Utils {
static Object parseHashMapToObject(HashMap map, Class cls) {
GsonBuilder gsonBuilder = new GsonBuilder();
Gson gson = gsonBuilder.create();
String jsonString = gson.toJson(map);
return gson.fromJson(jsonString, cls);
}
}
Gson Github: https://github.com/google/gson
You would traverse the map in your constructor and assign the values. If there's an actual library for doing this(almost like a Bean), then I've never heard of it.
Casting of a HashMap to a fruit wouldn't be possible.
The second is not possible but you can create a class that will take a Map as a constructor parameter.
class Fruit{
private Map<String, String> fruitMap;
Fruit(Map<String, String> map){
}
}
Assuming the keys in map correspond to setter methods in the Fruit class, you could use one of Apache bean's utilities like PropertyUtils.
final Fruit f = new Fruit();
for(String key : map.keySet()) {
PropertyUtils.setProperty(fruit, key, map.get(key));
}
For very complicated cases of this you might want to take a look at Dozer. We use Dozer to map very large Maps to very large objects.