I'm fiddling around with an idea but can't get a grip on it.
I have an xml file with 100+ properties defining the runtime environment of a somewhat large program. These are exposed as variables through a class . At the moment, for each option in the xml file, there is a variable in the class plus public getter and private setter.
Each time we need a new option, we have to define it in the xml file and create the variable plus methods in the RuntimenEnvironment class.
Now, what I would like to do is something like this: I want to rewrite the class in such a way, that it exposes new options from the xml file as vars without having to touch the class.
My xml file uses this structure:
<option>
<name>theName</name>
<type>eg int</type>
<value>20</value>
<constant>THE_NAME</constant>
</option>
Can I write code in java that dynamically creates the vars at runtime and exposes them through a method without actually writing the method?
Is this possible?
Thanks in advance,
Chris
Couple of options I could think of are:
If the name is unique a map can be populated with name as the key.
If you are interested only in options then a list of Options can be
populated from the XML.
Below is the sample code implemented with SAX parser
Handler Class
public class OptionsParser extends DefaultHandler {
private final StringBuilder valueBuffer = new StringBuilder();
private final Map<String, Option> resultAsMap = new HashMap<String, Option>();
private final List<Option> options = new ArrayList<Option>();
//variable to store the values from xml temporarily
private Option temp;
public List<Option> getOptions() {
return options;
}
public Map<String, Option> getResultAsMap() {
return resultAsMap;
}
#Override
public void startElement(final String uri, final String localName, final String qName,
final Attributes attributes) throws SAXException {
if("option".equalsIgnoreCase(qName)) {
temp = new Option();
}
}
#Override
public void endElement(final String uri, final String localName, final String qName)
throws SAXException {
//read the value into a string to set them to option object
final String value = valueBuffer.toString().trim();
switch (qName) {
case "name":
temp.setName(value);
// set the value into map and name of the option is the key
resultAsMap.put(value, temp);
break;
case "type":
temp.setType(value);
break;
case "value":
temp.setValue(value);
break;
case "constant":
temp.setConstant(value);
break;
case "option":
// this is the end of option tag add it to the list
options.add(temp);
temp = null;
break;
default:
break;
}
//reset the buffer after every iteration
valueBuffer.setLength(0);
}
#Override
public void characters(final char[] ch, final int start, final int length)
throws SAXException {
//read the value into a buffer
valueBuffer.append(ch, start, length);
}
}
Option POJO
public class Option {
private String name;
private String type;
private String value;
private String constant;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
public String getConstant() {
return constant;
}
public void setConstant(String constant) {
this.constant = constant;
}
}
Input XML
<options>
<option>
<name>option1</name>
<type>int</type>
<value>20</value>
<constant>const1</constant>
</option>
<option>
<name>option2</name>
<type>string</type>
<value>testValue</value>
<constant>const2</constant>
</option>
</options>
Sample Main class
public class ParseXML {
public static void main(String[] args) {
final OptionsParser handler = new OptionsParser();
try {
SAXParserFactory.newInstance().newSAXParser()
.parse("C:/luna/sample/inputs/options.xml", handler);
} catch (SAXException | IOException | ParserConfigurationException e) {
System.err.println("Somethig went wrong while parsing the input file the exception is -- " + e.getMessage() + " -- ");
}
Map<String, Option> result = handler.getResultAsMap();
Collection<Option> values = result.values();
for (Option option : values) {
System.out.println(option.getName());
}
}
}
I'll talk about json configuration files. But XML should also be similar. Jackson provides way to deserialise JSON and create dynamic object.
If the names of your options (theName) are unique, you can create dynamic beans. Your xml will then look like:
<theName>
<type>eg int</type>
<value>20</value>
<constant>THE_NAME</constant>
</theName>
See, I am talking about json, so its actually:
theName: {
type: "int"
value: 20
constant: "THE_NAME" }
Dynamic beans contain a Map, so your options will be stored in a Map<String, Option>, where Option is a POJO containing type, value and constant fields.
You should be able to access your options by iterating the map. There is no need to create variables dynamically.
This blog entry has got details about how to convert json to POJO
Related
I have a Strig variable.
String name = "xyz";
I want to convert it to json object
{"name" : "xyz"}
I am currently doing it by putting the name in the map and converting map to json.
Map<String, String > map = new HashMap<>();
map.put("name", name);
new Gson().toJson(map);;
Is there better way to do it?
You can use ObjectMapper from com.fasterxml.jackson as
public static String getString(Object object) {
try {
//Object to JSON in String
return objectMapper.writeValueAsString(object);
} catch (IOException e) {
log.error("Exception ", e);
return "";
}
}
You should create an Class Name. So it will automatically create mapping for you:
Sample Code:
public static void main(String[] args) {
Name name = new Name();
name.setName("xyz");
System.out.println(new Gson().toJson(name));
}
Output: {"name":"xyz"}
Name Class:
public class Name {
private String name;
/**
* #return the name
*/
public String getName() {
return name;
}
/**
* #param name
* the name to set
*/
public void setName(String name) {
this.name = name;
}
}
If you have a JSON object of any complexity, you should create a POJO to hold it for you as suggested by #notescrew. If this is a one-off (for example, in some Web controllers that return basic status information) and you are on at least Java 9, you can simplify it by using an inline map:
gson.toJson(Map.of("name", name));
Note that most frameworks (client and server) will take care of serializing to JSON for you, so in most cases you can simply pass or return the Map directly.
I am trying to convert the following JSON structure (part of a larger JSON object) to a POJO but getting the exception copied below (using Java/Jackson).
JSON
"outputKeys":
{"ABC":"gGyIioUr4Jfr5QiCm6Z==",
"DEF":"RxHfNyD2JyPOpG5tv3Jaj5g=="}
Java class
private class OutputKeys {
private String key;
private String value;
public OutputKeys(String key, String value) {
this.key = key;
this.value = value;
}
}
&
ObjectMapper mapper = new ObjectMapper();
mapper.readValue(jsonString, Test.class);
exception:
no suitable constructor found, can not deserialize from Object value (missing default constructor or creator, or perhaps need to add/enable type information?
Test class has the OutputKeys as an attribute.
Any suggestions would be welcome. I have tried using a List of OutputKeys as well .
Update:
I have tried the following without success:
class OutputKeys {
public Map<String, String> keys;
///with constructor/setter/getters
}
&
class OutputKeys {
public List<OutputKey> keys;
///with constructor/setter/getters
public class OutputKey {
Map<String, String> outputs = new HashMap<>();
// tried this too:
// String key
//String value
}
You require below mentioned single class only, containing
All keys(ABC and DEF)
getters/setters
toString() which you'll use interact with JSON.
public class OutputKeys
{
private String ABC;
private String DEF;
public String getABC ()
{
return ABC;
}
public void setABC (String ABC)
{
this.ABC = ABC;
}
public String getDEF ()
{
return DEF;
}
public void setDEF (String DEF)
{
this.DEF = DEF;
}
#Override
public String toString()
{
return "ClassPojo [ABC = "+ABC+", DEF = "+DEF+"]";
}
}
Let me know if you require more details.
Since the keys were dynamic, I ended up deserializing the data using the iterator on the JsonNode:
jsonNode.get("outputKeys").iterator()
& then getting the relevant dynamic key information via the iterator.
I needed a similar tool for NodeJS. So that I can write tests on parts of a bigger model that was serialized (JSON).
So, if I need only "ABC":"gGyIioUr4Jfr5QiCm6Z==" or "XYZ":{"Hello": "My String", "Content": [1,2,3]}, the only property I care to test at the moment is:
var sutXYX = { Hello: "My String", Content: [ 1, 2, 2]};
I wrote this tool as a utility https://github.com/whindes/PojoScriptifyFromJSON
In my Android app I have json, which looks like :
{
"Records": [
{
"RowIndex": "0",
"NameValue": {
"Name": "PropertyName1",
"Value": "PropertyValue1"
}
}{
"RowIndex": "1",
"NameValue": {
"Name": "PropertyName2",
"Value": "PropertyValue2"
}
}
]
}
I need to parce this json to object, which looks like:
public class MyClass {
public String PropertyName1;
public String PropertyName2;
}
And result after parsing should be:
public String PropertyName1 = "PropertyValue1";
public String PropertyName2 = "PropertyValue2";
Basically, the first json is equivalent of:
{
"PropertyName1" : "PropertyValue1",
"PropertyName2" : "PropertyValue2"
}
Question: How can I parce first json without usage swith/case to search for the necessary Property?
You'll have to go down the dark path of reflection I'm afraid.
you can parse the json into an intermediary object which has a map for namevalue.
then you use the below code (ofcourse just copy paste the bits you need) to loop over the map of key/value pairs. for each key look up the field you want, and set it. If you're guaranteed only to need to set public variables then you can use getFields and can skip the setAccessible.
public class Test {
public static void main(String[] argv) {
MyClass myClass = new MyClass();
Class<?> classObject = myClass.getClass();
// Field fields[] = classObject.getFields(); // if you want to get only public fields.
Field fields[] = classObject.getDeclaredFields(); // any field
for(Field f : fields) {
System.out.println(f.getName());
try {
// if member is private: security managers may object but the default java allows it
f.setAccessible(true);
f.set(myClass, "abc");
} catch (IllegalAccessException e) {
// handle access exception:
e.printStackTrace();
}
}
System.out.println("prop 1: " + myClass.PropertyName1);
System.out.println("prop 2: " + myClass.PropertyName2);
}
public static class MyClass {
public String PropertyName1;
private String PropertyName2;
}
}
Actually.. there is a non-reflect way but that will replace your implementation of the object you have.
If you change your class:
public class MyClass {
public String PropertyName1;
public String PropertyName2;
}
to
public class MyClass {
private Map<String, String> properties = new HashMap<String, String>();
public void setProperties(Map<String, String> props) { this.properties = props; }
public String getPropertyName1() {
return lookupProperty("PropertyName1");
}
public String getPropertyName2() {
return lookupProperty("PropertyName2");
}
private String lookupProperty(String property) {
if (properties.containsKey(property) {
return properties.get(property);
} else {
return null;
}
}
}
then you could parse the name value map into a map, and construct a myclass with it.
just listing it for completeness, though changing your domain model to fit a json input is not ideal.
I would recommend either way to do the input parsing, and then copy over the model into your actual domain object rather than using the json-model in your application. that way if the json model ever changes, your domain model will not change.
One method I can think of (which doesn't sound too great) is to actually make an object that matches the JSON response you get back. Then, map THAT NameValue object to MyClass
So, something like
public class NameValue {
public string Name;
public String Value;
public MyClass getMyClass(){
MyClass myClass = new MyClass();
myClass.PropertyName2 = Value;
return myClass;
}
}
You can come up with a better way to map it, obviously. But this is just an example of something I might do if I was given a response JSON I didn't particularly care for. You can similarly reverse it (have MyClass be able to create a NameValue object) so you can send data back in the correct format.
I'm trying to serialize a rather large structure with Jackson.
However, it's also trying to export a lot of substructures I will never need (causing a JsonMappingException: No serializer found for class)
So how can I exclude classes and namespaces from serialization?
Alternatively, how can I flag properties of my classes as excluded/ignored?
Use the transient keyword if you have actually access to the substructure you want to exclude.
transient is a Java keyword which marks a member variable not to be
serialized when it is persisted to streams of bytes. When an object is
transferred through the network, the object needs to be 'serialized'.
Serialization converts the object state to serial bytes. Those bytes
are sent over the network and the object is recreated from those
bytes. Member variables marked by the java transient keyword are not
transferred, they are lost intentionally.
http://en.wikibooks.org/wiki/Java_Programming/Keywords/transient
Please give an example for exclude classes and namespace but for properties for which you might not control the source code you can use the following on types and fields
#JsonIgnoreProperties(value = {"propertyName", "otherProperty"})
Here's the javadoc.
Here's an example
#JsonIgnoreProperties(value = { "name" })
public class Examples {
public static void main(String[] args) throws JsonGenerationException, JsonMappingException, IOException {
Examples examples = new Examples();
examples.setName("sotirios");
Custom custom = new Custom();
custom.setValue("random");
custom.setNumber(42);
examples.setCustom(custom);
ObjectMapper mapper = new ObjectMapper();
StringWriter writer = new StringWriter();
mapper.writeValue(writer, examples);
System.out.println(writer.toString());
}
private String name;
#JsonIgnoreProperties(value = { "value" })
private Custom custom;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Custom getCustom() {
return custom;
}
public void setCustom(Custom custom) {
this.custom = custom;
}
static class Custom {
private String value;
private int number;
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
public int getNumber() {
return number;
}
public void setNumber(int number) {
this.number = number;
}
}
}
prints
{"custom":{"number":42}}
In other words, it ignored Examples#name and Custom#value.
I'm going to accept a csv file which will have certain values. Those values will be validated againsts the attributes of an object
Example:
If there is a person class which has name,email,phonenumber etc.
public class Person{
private String name;
private String email;
private String status;
set();
get();
}
And the csv file has "name","email", I want to write a validation logic which will check the content of the csv against the object attributes.
Using reflexion, you can see which fields are in the class:
Field[] fields = Person.class.getDeclaredFields();
for(Field curField:fields)
{
System.out.println(curField.getName());
}
You can then take the field name form the csv and compare its value.
I usually go with this solution. It's a predicate so it's reusable. Depends which predicate you use you can use it with guava or Apache Commons Collections.
public class BeanPropertyPredicate<T, V> implements Predicate<T> {
// Logger
private static final Logger log = LoggerFactory.getLogger(BeanPropertyPredicate.class);
public enum Comparison {EQUAL, NOT_EQUAL}
private final String propertyName;
private final Collection<V> values;
private final Comparison comparison;
public BeanPropertyPredicate(String propertyName, Collection<V> values, Comparison comparison) {
this.propertyName = propertyName;
this.values = values;
this.comparison = comparison;
}
#Override
public boolean apply(#Nullable T input) {
try {
PropertyDescriptor propertyDescriptor = PropertyUtils.getPropertyDescriptor(input, propertyName);
Object value = propertyDescriptor.getReadMethod().invoke(input);
switch (comparison) {
case EQUAL:
if(!values.contains(value)) {
return false;
}
break;
case NOT_EQUAL:
if(values.contains(value)) {
return false;
}
break;
}
} catch (Exception e) {
log.error("Failed to access property {}", propertyName, e);
}
return true;
}
}