I hava a class which has some String-Lists that I want to marshal via Jackson. And for better usage I want to have in eacht list the same Element-Name. So I annotate like this:
import java.util.List;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlElementWrapper;
public class MyClass
{
public String title;
#XmlElementWrapper(name="hints")
#XmlElement(name="text")
public List<String> hints;
#XmlElementWrapper(name="warnings")
#XmlElement(name="text")
public List<String> warnings;
#XmlElementWrapper(name="errors")
#XmlElement(name="text")
public List<String> errors;
}
But at runtime I get an exception Could not write JSON: Multiple fields representing property "text". I've also tried this with no effect:
// mapper instanceof com.fasterxml.jackson.dataformat.xml.XmlMapper
mapper.configure(MapperFeature.USE_WRAPPER_NAME_AS_PROPERTY_NAME, true);
What do I need in addition?
Not a perfect solution, but still a good workaround when I seperate the list itself in a new class and remove wrapping (for it is wrapped by the members using this new type):
public class StringList
{
#JacksonXmlElementWrapper(useWrapping = false)
#XmlElement(name="text")
public final List<String> list = new ArrayList<String>();
public void add( String msg )
{
list.add( msg );
}
}
... so my class will look like:
public class MyClass
{
public String title;
public StringList hints;
public StringList warnings;
public StringList errors;
}
Related
Jackson is doing something truly bizarre and I cannot find any explanation for it. I'm doing polymorphic serialization and it works perfectly when an object is on its own. But if you put the same object into a list and serialize the list instead, it erases the type information.
The fact that it's losing type info would lead one to suspect type erasure. But this is happening during serialization of the contents of the list; all Jackson has to do is inspect the current object it's serializing to determine its type.
I've created an example using Jackson 2.5.1:
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonSubTypes.Type;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.JsonTypeName;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.ArrayList;
import java.util.List;
public class Test {
#JsonIgnoreProperties(ignoreUnknown = true)
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY)
#JsonSubTypes({
#Type(value = Dog.class, name = "dog"),
#Type(value = Cat.class, name = "cat")})
public interface Animal {}
#JsonTypeName("dog")
public static class Dog implements Animal {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
#JsonTypeName("cat")
public static class Cat implements Animal {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
public static void main(String[] args) throws JsonProcessingException {
List<Cat> list = new ArrayList<>();
list.add(new Cat());
System.out.println(new ObjectMapper().writeValueAsString(list));
System.out.println(new ObjectMapper().writeValueAsString(list.get(0)));
}
}
Here's the output:
[{"name":null}]
{"#type":"cat","name":null}
As you can see, Jackson is not adding the type information when the object is in a list. Does anyone know why this is happening?
The various reasons for why this happens are discussed here and here. I don't necessarily agree with the reasons, but Jackson, because of type erasure, doesn't off the bat know the type of elements the List (or Collection or Map) contains. It chooses to use a simple serializer that doesn't interpret your annotations.
You have two options suggested in those links:
First, you can create a class that implements List<Cat>, instantiate it appropriately and serialize the instance.
class CatList implements List<Cat> {...}
The generic type argument Cat is not lost. Jackson has access to it and uses it.
Second, you can instantiate and use an ObjectWriter for the type List<Cat>. For example
System.out.println(new ObjectMapper().writerFor(new TypeReference<List<Cat>>() {}).writeValueAsString(list));
will print
[{"#type":"cat","name":"heyo"}]
The answer Sotirios Delimanolis gave is the correct one. However, I thought it'd be nice to post this workaround as a separate answer. if you are in an environment in which you cannot change the ObjectMapper for each type of thing you need to return (like a Jersey/SpringMVC webapp), there is an alternative.
You can simply include a private final field on the class that contains the type. The field won't be visible to anything outside the class, but if you annotate it with #JsonProperty("#type") (or "#class" or whatever your type field is named) Jackson will serialize it regardless of where the object is located.
#JsonTypeName("dog")
public static class Dog implements Animal {
#JsonProperty("#type")
private final String type = "dog";
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
I faced this problem as well and this is the workaround that I prefer (I'm using Kotlin but with Java it's pretty much the same)
The parent class configures #JsonTypeInfo to use an existing property as the marker to break the ambiguity of the sub types
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.EXISTING_PROPERTY)
#JsonSubTypes(
JsonSubTypes.Type(value = Bob::class, name = "bob"),
JsonSubTypes.Type(value = Alice::class, name = "alice")
)
abstract class Person {
abstract val jacksonMarker: String
#JsonProperty("#type")
get
// ... rest of the class
}
The subclasses:
class Bob: Person {
override val jacksonMarker: String
get() = "bob"
// ... rest of the class
}
class Alice: Person {
override val jacksonMarker: String
get() = "alice"
// ... rest of the class
}
And you're set.
Similar but a little simpler than #monitorjbl above.
#JsonIgnoreProperties(ignoreUnknown = true)
#JsonTypeInfo(use = NAME, include = PROPERTY, property = "type")
#JsonSubTypes({
#Type(value = Dog.class, name = "dog"),
#Type(value = Cat.class, name = "cat")})
public interface Animal {}
public static class Dog implements Animal {
private final String type = "dog";
}
public static class Cat implements Animal {
private final String type = "cat";
}
Sotirios Delimanolis answer is correct. If you are using Kotlin you can simply create a new type like this:
class CatList: List<Cat> by listOf()
As arrays do not use type erasure you can solve it overriding the ObjectMapper.writeValueAsString to transform the Collection into an Array.
public class CustomObjectMapper extends ObjectMapper {
#Override
public String writeValueAsString(Object value) throws JsonProcessingException {
//Transform Collection to Array to include type info
if(value instanceof Collection){
return super.writeValueAsString(((Collection)value).toArray());
}
else
return super.writeValueAsString(value);
}
}
Use it into your Test.main:
System.out.println(new CustomObjectMapper().writeValueAsString(list));
Output (cats and dogs list):
[{"#type":"cat","name":"Gardfield"},{"#type":"dog","name":"Snoopy"}]
Can some one correct this statement for me?
List<Object> objectList = new ArrayList<Object>();
objectList.addAll(Arrays.asList(["206C(1)", "CG", 1],["206C(1)", "SG", 1]));
after getting this data in objectlist i need to use .stream() like operations
Object will be a class of
public class Object{
private String section;
private String code;
private Boolean yn;
getters and setters
}
any help is truly appreciated
In general a class' name should give an idea of the class' content/purpose. That is such generic names as Object are a bad idea in most cases. Additionally Object might easily get confused with java.lang.Object...
public class MyObject {
private String section;
private String code;
private Boolean yn;
//getters and setters
public MyObject(String section, String code, Boolean yn) {
this.section = section;
this.code = code;
this.yn = yn;
}
}
With the above constructor you can do:
List<Object> objectList = List.of(new MyObject("206C(1)", "CG", true), new MyObject("206C(1)", "SG", true));
If you make a static import for a factory method, you can save some characters:
public class MyObject {
// all of the above
public static MyObject of(String section, String code, Boolean yn) {
return new MyObject(section, code, yn);
}
}
import static MyObject.of
...
List<Object> objectList = List.of(of("206C(1)", "CG", true), of("206C(1)", "SG", true));
I am using Jackson to convert json to an object. However, the json looks wrong. Here is what I am seeing:
"interfaces": {"interfaces": [
"HA_1",
"HA_2"
]},
There should not be two interfaces. I want to see:
"interfaces": [
"HA_1",
"HA_2"
]},
I am not sure how this is happening. I can show you my conversion classes:
#XmlAccessorType(XmlAccessType.NONE)
public class InterfacesRep implements Serializable {
private static final long serialVersionUID = -1503363608473342020L;
#XmlElement(name = "interface", type = String.class)
private Collection<String> all = new ArrayList<String>();
public InterfacesRep() {}
public InterfacesRep(Collection<String> all) {
this.all = all;
}
public Collection<String> getAll() {
return all;
}
public void setAll(List<String> all) {
this.all = all;
}
}
And the outer class:
public class OuterRep {
private static final long serialVersionUID = -1719378545790376294L;
#XmlElement(name = "interfaces", type=InterfacesRep.class)
private InterfacesRep interfaces;
public OuterRep() {
}
public InterfacesRep getInterfaces() {
return interfaces;
}
public void setInterfaces(InterfacesRep interfaces) {
this.interfaces = interfaces;
}
}
Do you know why I see "interfaces" twice?
Because you are defining it on the property at both levels.The outer class has a property name called "interfaces" and the inner class's Collection is also named "interfaces".
This simplest fix (in my mind) would be to not use a wrapper class for the Collection. Just put the collection in the outer class.
On a side note, why are you using Jackson's XML annotations to serialize JSON?
#XmlType
#XmlAccessorType(XmlAccessType.FIELD) // here I need this access
public class User implements Serializable
{
// ...
#XmlTransient
private Set<Values> values;
// ...
#XmlElement
private Set<History> getXmlHistory()
{
return new CustomSet<Values, History>(Values);
}
private void setXmlHistory(final Set<History> aHistory)
{
this.values = new HashSet<Values>();
}
}
When I am create User object in Java code and after create XML, then all normally.
But when I try to get User-object from XML, then field values always null. So setter not working here. May be setter needs some annotation too?
XML looks like
<user>
...
<xmlHistory>
// ... record 1
</xmlHistory>
<xmlHistory>
// ... record 2
</xmlHistory>
</user>
I do not believe that this is a JAXB problem, as the following model will work:
package forum10617267;
import java.io.Serializable;
import java.util.*;
import javax.xml.bind.annotation.*;
#XmlType
#XmlAccessorType(XmlAccessType.FIELD) // here I need this access
public class User implements Serializable {
#XmlTransient
private Set<History> history = new HashSet<History>();
#XmlElement
private Set<History> getXmlHistory() {
return history;
}
private void setXmlHistory(final Set<History> aHistory) {
this.history = aHistory;
}
}
The problem you are seeing is a result of the logic you have in your get/set methods. Since your values field is not initialized, I am not sure how CustomSet would be able to update it.
package forum10617267;
import java.io.Serializable;
import java.util.*;
import javax.xml.bind.annotation.*;
#XmlType
#XmlAccessorType(XmlAccessType.FIELD) // here I need this access
public class User implements Serializable {
#XmlTransient
private Set<Values> values;
#XmlElement
private Set<History> getXmlHistory() {
return new CustomSet<Values, History>(values);
}
private void setXmlHistory(final Set<History> aHistory) {
this.values = new HashSet<Values>();
}
}
Using hibernate, how can I persist a class with a List<String> field?
Consider the following entity class:
#Entity
public class Blog {
private Long id;
private List<String> list;
#Id
#GeneratedValue
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public List<String> getList() { return list; }
public void setList(List<String> list) { this.list = list; }
}
However, when I attempt to save it, I get the following error:
[INFO] An exception occured while executing the Java class. null
Could not determine type for: java.util.List, at table: Blog, for columns: [org.hibernate.mapping.Column(list)]
I tried adding '#CollectionOfElements' to getList(), but then only the id is saved to the library. No corresponding column is created for the list.
Note: I'm just trying Hibernate, so I could use documentation links that we will help me understand the collection relationship management in Hibernate
Have a look at This. Maybe it is of help.
Did you apply #CollectionOfElements as follows?
#org.hibernate.annotations.CollectionOfElements(
targetElement = java.lang.String.class
)
Have a look at the Hibernate Annotations Documentation about Collections basically you have to tell the list in what relation it stands to.
#OneToMany(mappedBy="blog")
public List<String> getList() { return list; }
Then it should work.
Use a Serializable object seems to work better. Changing list property to ArrayList<String> seems to solve the problem.
package com.company.directDataLoader.model.admin;
import java.util.Arrays;
import java.util.Collection;
import javax.persistence.AttributeConverter;
import static java.util.Collections.emptyList;
public class StringListConverter implements AttributeConverter<Collection<String>, String> {
private static final String SPLIT_CHAR = ";";
#Override
public String convertToDatabaseColumn(Collection<String> stringList) {
return stringList != null ? String.join(SPLIT_CHAR, stringList) : "";
}
#Override
public Collection<String> convertToEntityAttribute(String string) {
return string != null ? Arrays.asList(string.split(SPLIT_CHAR)) : emptyList();
}
}
In the entity class:
#Column(name="table_keys")
#Convert(converter = StringListConverter.class)
private Collection<String> tableKeys;