relate toString with object creation - java

I have a fairly basic Java class with some class variables. I have overwridden toString() to provide me with a string output (which will eventually be output to a text file).
I am trying to elegantly create a way for me to use this string output to recreate the object with all of the variables set as before. The class looks something like this:
public class Report {
private String itemA;
private String itemB;
private String itemC;
#Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("Items are::");
sb.append("\nItem A is: ").append(itemA);
sb.append("\nItem B is: ").append(itemB);
sb.append("\nItem C is: ").append(itemC);
return sb.toString();
}
}
this is how I can potentially tackle it using reflection:
public class Report {
private String itemA;
private String itemB;
private String itemC;
private final Map<String, String> MAPPING = new HashMap<>();
public Report(String itemA, String itemB, String itemC) {
this.itemA = itemA;
this.itemB = itemB;
this.itemC = itemC;
MAPPING.put("Item A is: ", "itemA");
MAPPING.put("Item B is: ", "itemB");
MAPPING.put("Item C is: ", "itemC");
}
#Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("Items are::");
MAPPING.entrySet().forEach(entry -> {
sb.append("\n").append(entry.getKey()).append(BeanUtils.getProperty(this, entry.getValue()));
});
return sb.toString();
}
public Report createReportFromString(String reportString) {
List<String> reportLines = Arrays.asList(reportString.split("\n"));
HashMap<String, String> stringObjectRelationship = new HashMap<>();
reportLines.forEach(reportLine -> {
Optional<String> matchingKey = MAPPING.keySet().stream().filter(reportLine::contains).findFirst();
matchingKey.ifPresent(key -> {stringObjectRelationship.put(MAPPING.get(key), reportLine.split(key)[1]);});
});
stringObjectRelationship.forEach((variableName, variableValue) -> BeanUtils.setProperty(this, variableName, variableValue));
return this;
}
}
I basically want to relate the key in the report ("Item A is: ") to the name of the corresponding variable ("itemA") and use this relationship in both the toString() method and the createReportFromString(String string) method. Now when doing this there are a lot of possible exceptions that can be thrown and need to either be handled or thrown - and it then looks a lot less elegant than I would like.
I don't know if this is possible to do without reflection - or perhaps I could rearrange this class to make this possible?
What I can`t change is the structure of the string output in the toString().

Reflection bears multiple features:
Automatic discovery of features of a program at runtime
Support for dealing with features unknown at compile-time
Provide an abstraction of program features (e.g. methods or fields)
Your approach suggests that you don’t want an automatic discovery, as you are specifying the three elements explicitly. This is a good thing, as it makes your program more robust regarding future changes, as dealing with automatically discovered, potentially unknown program elements will destroy any help from the compiler, as it can’t tell you when there are mismatches.
You only want the third point, an abstraction over the elements of your report. You can create such an abstraction yourself, tailored to your use case, without Reflection, which will be more robust and even more efficient:
public class Report {
static final class Element {
final String header;
final Function<Report,String> getter;
final BiConsumer<Report,String> setter;
final Pattern pattern;
Element(String header,
Function<Report, String> getter, BiConsumer<Report, String> setter) {
this.header = header;
this.getter = getter;
this.setter = setter;
pattern = Pattern.compile("^\\Q"+header+"\\E(.*?)$", Pattern.MULTILINE);
}
}
static final List<Element> ELEMENTS = List.of(
new Element("Item A is: ", Report::getItemA, Report::setItemA),
new Element("Item B is: ", Report::getItemB, Report::setItemB),
new Element("Item C is: ", Report::getItemC, Report::setItemC));
private String itemA, itemB, itemC;
public Report(String itemA, String itemB, String itemC) {
this.itemA = itemA;
this.itemB = itemB;
this.itemC = itemC;
}
#Override public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("Items are:");
ELEMENTS.forEach(e ->
sb.append('\n').append(e.header).append(e.getter.apply(this)));
return sb.toString();
}
public static Report createReportFromString(String reportString) {
return new Report("", "", "").setValuesFromString(reportString);
}
public Report setValuesFromString(String reportString) {
Matcher m = null;
for(Element e: ELEMENTS) {
if(m == null) m = e.pattern.matcher(reportString);
else m.usePattern(e.pattern).reset();
if(!m.find())
throw new IllegalArgumentException("missing \""+e.header+'"');
e.setter.accept(this, m.group(1));
}
return this;
}
public String getItemA() {
return itemA;
}
public void setItemA(String itemA) {
this.itemA = itemA;
}
public String getItemB() {
return itemB;
}
public void setItemB(String itemB) {
this.itemB = itemB;
}
public String getItemC() {
return itemC;
}
public void setItemC(String itemC) {
this.itemC = itemC;
}
}
This works with Java’s out-of-the-box features, not requiring another library to simplify the operation.
Note that I changed the code pattern, as createReportFromString is a misleading name for a method modifying an already existing object. I used the name for a factory method truly creating a new object and added a another method for setting the values of the object (as a direct counter-part to toString).
If you are still using Java 8, you can replace List.of(…) with Arrays.asList(…) or better Collections.unmodifiableList(Arrays.asList(…)).
You can also remove the .reset() call in the setValuesFromString method. When you remove it, the elements in the input string are required to be in the same order as the toString() method produces. This makes it a bit less flexible, but also more efficient if you expand the code to have a lot more elements.

#JimboMcHiggins assuming I can change the toString output how exactly would you tie together serialization and deserialization with some common mapping?
I would leave the toString unchanged and move the responsibility of serialization to java.io.Serializable. Correct me if this is not an acceptable approach. The mapping would be defined by the class fields of your Report pojo. This would also allow you to change your toString without breaking deserialization of existing objects.
import java.io.Serializable;
public class Report implements Serializable {
private static final long serialVersionUID = 1L;
private String itemA;
private String itemB;
private String itemC;
public Report(String itemA, String itemB, String itemC) {
this.itemA = itemA;
this.itemB = itemB;
this.itemC = itemC;
}
#Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("Items are::");
sb.append("\nItem A is: ").append(itemA);
sb.append("\nItem B is: ").append(itemB);
sb.append("\nItem C is: ").append(itemC);
return sb.toString();
}
}
Example Usage
public class Test1 {
public static void main(String[] args) {
Report report = new Report("W", "O", "W");
System.out.println(report);
String filename = "file.ser";
// Serialization
try
{
//Saving of report in a file
FileOutputStream file = new FileOutputStream(filename);
ObjectOutputStream out = new ObjectOutputStream(file);
// Method for serialization of report
out.writeObject(report);
out.close();
file.close();
System.out.println("Report has been serialized");
}
catch(IOException ex)
{
System.out.println("IOException is caught");
}
Report report1 = null;
// Deserialization
try
{
// Reading the report from a file
FileInputStream file = new FileInputStream(filename);
ObjectInputStream in = new ObjectInputStream(file);
// Method for deserialization of report
report1 = (Report)in.readObject();
in.close();
file.close();
System.out.println("Report has been deserialized ");
System.out.println(report1);
}
catch(IOException ex)
{
System.out.println("IOException is caught");
}
catch(ClassNotFoundException ex)
{
System.out.println("ClassNotFoundException is caught");
}
}
}
Output
Items are::
Item A is: W
Item B is: O
Item C is: W
Report has been serialized
Report has been deserialized
Items are::
Item A is: W
Item B is: O
Item C is: W

Related

Design Enum generic behavior

Well my question today is:
I have an enum with file headers.
I have a function which export theses headers as String
Running multiple project I want to make it quite generic to be used in our private lib.
Current Function:
private static String getHeaders() {
StringBuilder sb = new StringBuilder();
for(HeadersEnum header : HeadersEnum.values()){
sb.append(header.getExportLib());
}
return sb.toString();
}
Goal something like it:
private static String getHeaders(ExportableCSV<Enum<T>> data) {
StringBuilder sb = new StringBuilder();
for( ExportableCSV<Enum> header : data.values()){
sb.append(header.getExportLib());
}
return sb.toString();
}
I know we can't make inheritance with Enum so I created an Interface "ExportableCSV":
public interface ExportableCSV<T extends Enum<T>> {
public static final String exportLib = "";
public static String getExportLib() {
return exportLib;
}
}
It's quite basic, but in my mind , any Enum which implement this ExportableCSV should have access to my exportLib() function.
public enum HeadersEnum implements ExportableCSV<HeadersEnum>{
foo("foo;"),
bar("bar;");
private String exportLib;
of course my goal approach of generic function isn't compiling and I'm not really understanding what I can do and what I can't.
One simple option is to have all relevant Enums implement an export interfce
interface Exportable { String getExportLib(); }
Another option: If you want a static method that uses values you can do something like:
class EnumUtil {
public static <T extends Enum<T>> String getExportLib(T[] aValues){
StringBuilder sb = new StringBuilder();
for( T t : aValues){
//todo
}
return sb.toString();
}
}

Java serialization read enum to string

There is some legacy Java pojos which was used for binary serialization. Among the fields of one pojo, I have one enum field. Now in the new Java pojo, the enum field is replaced by a string field.
// old pojo with enum
class Test {
private DataType dataType;
private String someOtherField;
}
enum DataType {
Int,
Float,
String
}
// new pojo without enum field
class NewTest {
private String dataType;
private String someOtherField;
}
While reading(de-serializing) the old data, I have used the techniques mentioned here - https://stackoverflow.com/a/14608062/314310 to read old data into the new refactored pojo, which performs successfully for the non enum fields. But reading enum data to string field is almost seems impossible. I am getting exception as
java.lang.ClassCastException: cannot assign instance of demo.DataType to field demo.NewTest.dataType of type java.lang.String in instance of demo.NewTest
Is there anyway I can achieve this?
EDIT:
Here is the code for my custom ObjectInputStream
class MyObjectInputStream extends ObjectInputStream {
private static final Map<String, Class<?>> migrationMap = new HashMap<>();
static {
migrationMap.put("demo.Test", NewTest.class);
migrationMap.put("demo.DataType", String.class);
}
public MyObjectInputStream(InputStream stream) throws IOException {
super(stream);
}
#Override
protected ObjectStreamClass readClassDescriptor() throws IOException, ClassNotFoundException {
ObjectStreamClass resultClassDescriptor = super.readClassDescriptor();
for (final String oldName : migrationMap.keySet()) {
if (resultClassDescriptor != null && resultClassDescriptor.getName().equals(oldName)) {
Class<?> replacement = migrationMap.get(oldName);
try {
resultClassDescriptor = ObjectStreamClass.lookup(replacement);
} catch (Exception e) {
log.error("Error while replacing class name." + e.getMessage(), e);
}
}
}
return resultClassDescriptor;
}
}
Try changing your enum into this:
enum DataType {
Int,
Float,
String;
public static DataType getFromString(String stringDataType) {
for(DataType dataType in DataType.values()) {
if (dataType.toString().equals(stringDataType)) {
return dataType;
}
}
throw new IllegalArgumentException("Invalid input");
}
}
So when you want to assign the Enum to String you call:
newTest.dataType = test.dataType.toString();
And when you want to assign the String to Enum, you call:
test.dataType = DataType.getFromString(newTest.dataType);

Writing generic POJO to CSV transformer

My use case was to write a generic CSV transformer, which should be able to convert any Java POJO to CSV string.
My Implementation :
public <T> List<String> convertToString(List<T> objectList) {
List<String> stringList = new ArrayList<>();
char delimiter = ',';
char quote = '"';
String lineSep = "\n";
CsvMapper mapper = new CsvMapper();
CsvSchema schema = mapper.schemaFor(!HOW_TO!);
for (T object : objectList) {
try {
String csv = mapper.writer(schema
.withColumnSeparator(delimiter)
.withQuoteChar(quote)
.withLineSeparator(lineSep)).writeValueAsString(object);
} catch (JsonProcessingException e) {
System.out.println(e);
}
}
return stringList;
}
I was using Jackson-dataformat-csv library, but I'm stuck with !HOW_TO! part, ie How to extract the .class of the object from the objectList. I was studying and came across Type Erasure, So I think it is somehow not possible other than giving the .class as parameter to my function. But I'm also extracting this object list from generic entity using Java Reflection, so I can't have the option to provide the .class params.
Is there a workaround for this?
OR
Any other approaches/libraries where I can convert a generic List<T> objectList to List<String> csvList with functionality of adding delimiters, quote characters, line separators etc.
Thanks!
I have created a CSVUtil Class similar to below which uses java reflection.
Example to use below CSVUtil
Assuming POJO Student ,
List<Student> StudentList = new ArrayList<Student>();
String StudentCSV = CSVUtil.toCSV(StudentList,' ',false);
import java.lang.reflect.Field;
import java.util.List;
import java.util.logging.Logger;
CSVUtil class
public class CSVUtil {
private static final Logger LOGGER = Logger.getLogger(CSVUtil.class .getName());
private final static char DEFAULT_SEPARATOR = ' ';
public static String toCSV(List<?> objectList, char separator, boolean displayHeader) {
StringBuilder result =new StringBuilder();
if (objectList.size() == 0) {
return result.toString();
}
if(displayHeader){
result.append(getHeaders(objectList.get(0),separator));
result.append("\n");
}
for (Object obj : objectList) {
result.append(addObjectRow(obj, separator)).append("\n");
}
return result.toString();
}
public static String getHeaders(Object obj,char separator) {
StringBuilder resultHeader = new StringBuilder();
boolean firstField = true;
Field fields[] = obj.getClass().getDeclaredFields();
for (Field field : fields) {
field.setAccessible(true);
String value;
try {
value = field.getName();
if(firstField){
resultHeader.append(value);
firstField = false;
}
else{
resultHeader.append(separator).append(value);
}
field.setAccessible(false);
} catch (IllegalArgumentException e) {
LOGGER.severe(e.toString());
}
}
return resultHeader.toString();
}
public static String addObjectRow(Object obj, char separator) {
StringBuilder csvRow =new StringBuilder();
Field fields[] = obj.getClass().getDeclaredFields();
boolean firstField = true;
for (Field field : fields) {
field.setAccessible(true);
Object value;
try {
value = field.get(obj);
if(value == null)
value = "";
if(firstField){
csvRow.append(value);
firstField = false;
}
else{
csvRow.append(separator).append(value);
}
field.setAccessible(false);
} catch (IllegalArgumentException | IllegalAccessException e) {
LOGGER.severe(e.toString());
}
}
return csvRow.toString();
}
}
There is a simple option. I've added some lines to your code to show it :
public <T> List<String> convertToString(List<T> objectList) {
if(objectList.isEmpty())
return Collections.emptyList();
T entry = objectList.get(0);
List<String> stringList = new ArrayList<>();
char delimiter = ',';
char quote = '"';
String lineSep = "\n";
CsvMapper mapper = new CsvMapper();
CsvSchema schema = mapper.schemaFor(entry.getClass());
for (T object : objectList) {
try {
String csv = mapper.writer(schema
.withColumnSeparator(delimiter)
.withQuoteChar(quote)
.withLineSeparator(lineSep)).writeValueAsString(object);
stringList.add(csv);
} catch (JsonProcessingException e) {
System.out.println(e);
}
}
return stringList;
}
The trick is to get one of the elements of the list. In order to avoid crashs I've added a little data integrity test at the beginning that return an unmodifiable empty list in the case there are no items in the input list.
Then you retrieve an instance of your Object and use that to get the class.
Alternatively if the convertToString method is in a parametrized class you can do that in a slightly different way
public class GenericClass<T> {
private final Class<T> type;
public GenericClass(Class<T> type) {
this.type = type;
}
public Class<T> getMyType() {
return this.type;
}
}
This solution allow you to get the class of T. I don't think you'll need it for this question but it might comes in handy.
It seems this problem is just harder than most people would like it to be as a result of how Java does generics. Bruno's answer shows options that might work if you can make certain assumptions or can structure your code a certain way.
Another option that should work for your case can be found by way of the answers to this other question: How to get a class instance of generics type T
In there you'll find a link to an article: http://blog.xebia.com/acessing-generic-types-at-runtime-in-java/
This describes how to use the ParameterizedType of an object's superclass. You can apply that to your List object and hopefully it will work for you. This only may luckily work in this case, because you're taking as a parameter an object with a superclass whose type parameters match what you need.
Truly in general, we can't rely on knowing the type parameters at runtime. We can at best maybe use type tokens (parameter of type Class<T>)

Is it possible convert a json to java class object if class is not available

This Json
{
"age":"23",
"name":"srinivas",
"blog":"A",
"messages":["msg1","msg2","msg3"]
}
I want convert a json to java class like this class and class use:
public class A
{
private String name;
private String age;
private String blog;
private String[] messages;
public String getName ()
{
return name;
}
public void setName (String name)
{
this.name = name;
}
public String getAge ()
{
return age;
}
public void setAge (String age)
{
this.age = age;
}
public String getBlog ()
{
return blog;
}
public void setBlog (String blog)
{
this.blog = blog;
}
}
No. You cant have class created automatically for you from your json.
However the below thing you might be needing that's related:
You need link
Excerpt from the link, below example code:
String carJson =
"{ \"brand\" : \"Mercedes\", \"doors\" : 5," +
" \"owners\" : [\"John\", \"Jack\", \"Jill\"]," +
" \"nestedObject\" : { \"field\" : \"value\" } }";
ObjectMapper objectMapper = new ObjectMapper();
try {
JsonNode node = objectMapper.readValue(carJson, JsonNode.class);
JsonNode brandNode = node.get("brand");
String brand = brandNode.asText();
System.out.println("brand = " + brand);
JsonNode doorsNode = node.get("doors");
int doors = doorsNode.asInt();
System.out.println("doors = " + doors);
JsonNode array = node.get("owners");
JsonNode jsonNode = array.get(0);
String john = jsonNode.asText();
System.out.println("john = " + john);
JsonNode child = node.get("nestedObject");
JsonNode childField = child.get("field");
String field = childField.asText();
System.out.println("field = " + field);
} catch (IOException e) {
e.printStackTrace();
}
You technically can create a class at runtime: see How to create a class dynamically in java or Creating classes dynamically with Java. However, if your code wants to use A in some way, it needs to be available during compilation. And if your code doesn't need to use A, why create it? You could implement some interface/extend some class which is available during compilation, but in your case there is no reasonable common interface and you could only access it using reflection.
A more reasonable alternative would be to generate a class from known JSON examples (or better, from some schema description) during your build process. Again, there are many ways to do this and you should search for one which fits your needs.

Jackson: Serialize comma separated string to json array

Currently I have form like below:
public class Form {
private String listOfItems;
public String getListOfItems() {
return listOfItems;
}
public void setListOfItems(String listOfItems) {
this.listOfItems= listOfItems;
}
}
For instanse listOfItems equals to the following string "1,2,3".
The goal is to serialize this form to following format:
{
"listOfItems": [1, 2, 3]
}
It would be good to know how to correctly do such thing? As I know it is possible to create some custom serializer then mark appropriate getter method with it, like this #JsonSerialize(using = SomeCustomSerializer).
But not sure whether it is correct approach, probably any default implementations already exist.
If you can edit your Form class:
public class Form {
private String listOfItems;
public String getListOfItems() {
return listOfItems;
}
public void setListOfItems(String listOfItems) {
this.listOfItems = listOfItems;
}
#JsonProperty("listOfItems")
public List<Integer> getArrayListOfItems() {
if (listOfItems != null) {
List<Integer> items = new ArrayList();
for (String s : listOfItems.split(",")) {
items.add(Integer.parseInt(s)); // May throw NumberFormatException
}
return items;
}
return null;
}
}
By default Jackson looks for getters for serializing. You can override this by using #JsonProperty annotation.
ObjectMapper mapper = new ObjectMapper();
Form form = new Form();
form.setListOfItems("1,2,3");
System.out.print(mapper.writeValueAsString(form));
Outputs:
{"listOfItems":[1,2,3]}

Categories

Resources