Java 14 records with org.json - java

I wanted to make new JSON object with org.json library, but I have noticed there is a problem with Java 14 records.
When I call
String json = new JSONObject(new Order("", "Albert", "GOOGL", "SELL", 97.9, 90L)).toString();
the fields are null.
I suppose it is because java record doesn't use old getters like getXYZ?
Is there a simple work around? I mean without using different library.
Or maybe my assumptions are incorrect.
public record Order(
String id,
String userId,
String securityId,
String type,
Double price,
Long quantity
) {
}
<dependency>
<groupId>org.json</groupId>
<artifactId>json</artifactId>
<version>20220320</version>
</dependency>

If you don't want to use other libraries like Jackson or Gson (it will be a much better solution in my opinion) you could write your own converter:
public final class JsonConverter {
private JsonConverter() {
}
#SneakyThrows
public static String toJSON(Object object) {
Class<?> c = object.getClass();
JSONObject jsonObject = new JSONObject();
for (Field field : c.getDeclaredFields()) {
field.setAccessible(true);
String name = field.getName();
Object value = field.get(object);
jsonObject.put(name, value);
}
return jsonObject.toString();
}
}
You could use it like:
public static void main(String[] args) {
Order order = new Order("", "Albert", "GOOGL", "SELL", 97.9, 90L);
System.out.println(order);
JSONObject jsonObject = new JSONObject(order);
System.out.println(jsonObject.toString());
System.out.println(JsonConverter.toJSON(order));
}
Output:
OrderRecord[id=, userId=Albert, securityId=GOOGL, type=SELL, price=97.9, quantity=90]
{}
{"quantity":90,"price":97.9,"securityId":"GOOGL","id":"","type":"SELL","userId":"Albert"}
It is a real workaround. However, it uses exactly org.json.

According to documentation JSONObject(Object bean) constructor
construct a JSONObject from an Object using bean getters.
So, yes as you noticed it expects getter to be in format getFieldName(), but in records getters are named differently.
For, example getter for filed price would be price(), not getPrice().
One possible solution is to add getters in record class, like this :
public record Order(
String id,
String userId,
String securityId,
String type,
Double price,
Long quantity
) {
public Double getPrice() { // Compliant
return price;
}
public String getId() { // Compliant
return id;
}
public String getSecurityId() { // Compliant
return securityId;
}
public String getType() { // Compliant
return type;
}
public Long getQuantity() { // Compliant
return quantity;
}
}
You should see output like this :

Related

Output a field to multiple json fields

Is there a way using, Jackson annotations, to have a have given field serialised to 2 json fields.
Like the reverse of #JsonAlias which deserialises multiple json field to the one field.
Eg
class Foo {
#SomeAnnotation("field2")
String field1;
}
serialising as:
{
"field1" : "xyz",
"field2" : "xyz"
}
Is there a something like #SomeAnnotation?
——
Note: This is not a “should” I do this (it’s an imposed requirement), it’s a “how” I do this (elegantly).
A simple solution to this may be to just add two getters for field1:
class Foo {
private String field1 = "blah";
public String getField1() {
return field1;
}
public String getField2() {
return field1;
}
}
Jackson will create a field for each getter, following the javabeans naming convention:
{"field1":"blah","field2":"blah"}
An alternative to this may be #com.fasterxml.jackson.annotation.JsonAnyGetter, which can afford you even more flexibility:
class Foo {
private String field1 = "blah";
public String getField1() {
return field1;
}
#JsonAnyGetter
public Map<String, Object> getAny() {
Map<String, Object> m = new HashMap<>();
m.put("field2", this.field1);
m.put("field3", this.field1.toUpperCase());
return m;
}
}
Producing:
{"field1":"blah","field3":"BLAH","field2":"blah"}

Parse JSON Data using POJO, Where key name is numeric

I'm trying to parse a special JSON data using Wrapper class, special means a JSON which have numeric keys like below :
{
"date":"2018-11-01",
"hours":"01-Nov 08:00",
"1011":"837.7500",
"1022":"99.92596979567664",
"1010":"3.198083",
"1021":"5",
"1019":"1171.000",
"1018":"3.578371",
"1017":"30.46989",
"1016":"0.0001931423",
"1015":"6749",
"1014":"0.161805",
"1013":"0.001678397",
"1012":"1.406077"
}
I know how to parse JSON data using POJO, But in this case java is not accepting the numeric as Keys.
Wrapper/POJO Class
I don't want to go with JSON object based parsing. Is Anyone have any idea about it?
The Gson library has SerializedName functionality in which it parses the corresponding value of the key defined in SerializeName's parameter. Things will be tougher if your key is a pure integer since Java disallows it as variable name, in this case SerializeName will save you from that headache and it makes your code way more maintainable.
Example usage :
#SerializedName("1011") double lat;
#SerializedName("1022") double lng;
Try Gson for create wrapper class
http://www.jsonschema2pojo.org/
public class Example {
#SerializedName("date")
#Expose
private String date;
#SerializedName("hours")
#Expose
private String hours;
#SerializedName("1011")
#Expose
private String _1011;
#SerializedName("1022")
#Expose
private String _1022;
public String getDate() {
return date;
}
public void setDate(String date) {
this.date = date;
}
public String getHours() {
return hours;
}
public void setHours(String hours) {
this.hours = hours;
}
public String get1011() {
return _1011;
}
public void set1011(String _1011) {
this._1011 = _1011;
}
public String get1022() {
return _1022;
}
public void set1022(String _1022) {
this._1022 = _1022;
}
Have a look at this code ,hope it works for you
JSONObject jsonObjectData = jsonObject1.getJSONObject("yourObject");
Iterator iterator = jsonObjectData.keys();
while (iterator.hasNext()) {
YourClass yourClass = new YourClass();
String key = (String) iterator.next();
yourClass.setKey(key);
yourClass.setVajue(jsonObjectData.getString(key));
yourList.add(yourClass);
}

deserialize Json into POJO

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

Setting Default value to a variable when deserializing using gson

I am trying to convert JSON to Java object. When a certain value of a pair is null, it should be set with some default value.
Here is my POJO:
public class Student {
String rollNo;
String name;
String contact;
String school;
public String getRollNo() {
return rollNo;
}
public void setRollNo(String rollNo) {
this.rollNo = rollNo;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSchool() {
return school;
}
public void setSchool(String school) {
this.school = school;
}
}
Example JSON object:
{
"rollNo":"123", "name":"Tony", "school":null
}
So if school is null, I should make this into a default value, such as "school":"XXX". How can I configure this with Gson while deserializing the objects?
If the null is in the JSON, Gson is going to override any defaults you might set in the POJO. You could go to the trouble of creating a custom deserializer, but that might be overkill in this case.
I think the easiest (and, arguably best given your use case) thing to do is the equivalent of Lazy Loading. For example:
private static final String DEFAULT_SCHOOL = "ABC Elementary";
public String getSchool() {
if (school == null) school == DEFAULT_SCHOOL;
return school;
}
public void setSchool(String school) {
if (school == null) this.school = DEFAULT_SCHOOL;
else this.school = school;
}
Note: The big problem with this solution is that in order to change the Defaults, you have to change the code. If you want the default value to be customizable, you should go with the custom deserializer as linked above.
I think that the way to do this is to either write your no-args constructor to fill in default values, or use a custom instance creator. The deserializer should then replace the default values for all attributes in the JSON object being deserialized.
I was having the same issue, until I found this great solution.
For reference, you can create a post-processing class:
interface PostProcessable {
fun gsonPostProcess()
}
class PostProcessingEnabler : TypeAdapterFactory {
fun <T> create(gson: Gson, type: TypeToken<T>): TypeAdapter<T> {
val delegate = gson.getDelegateAdapter(this, type)
return object : TypeAdapter<T>() {
#Throws(IOException::class)
fun write(out: JsonWriter, value: T) {
delegate.write(out, value)
}
#Throws(IOException::class)
fun read(`in`: JsonReader): T {
val obj = delegate.read(`in`)
if (obj is PostProcessable) {
(obj as PostProcessable).gsonPostProcess()
}
return obj
}
}
}
}
Register it like this:
GsonBuilder().registerTypeAdapterFactory(PostProcessingEnabler())
Implement it on your model:
class MyClass : Serializable, PostProcessable {
// All your variable data
override fun gsonPostProcess() {
// All your post processing logic you like on your object
// set default value for example
}
}
And finally use it when converting json string:
var myObject = myGson.fromJson(myObjectJson, MyClass::class)
Or using retrofit2:
val api = Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(
GsonConverterFactory.create(
GsonBuilder().registerTypeAdapterFactory(
GsonPostProcessingEnabler()
).create()
)
)
.client(OkHttpClient.Builder().build())
.build()
.create(AccountApi::class.java)
You can simply make a universal function that checks for null
model.SchoolName= stringNullChecker(model.SchoolName);
public static String stringNullChecker(String val) {
if (null == val) val = "";
return val;
}

Validation when decoding json using jackson

I receive a json encoded string and then I decode it into a pojo, like this:
String json = ...
final ObjectMapper mapper = new ObjectMapper();
MyPojo obj = mapper.readValue(json, MyPojo.class);
I want to be able to validate this input, but I'm not sure what's the "right way" of doing it.
Let's say MyPojo is defined like this:
#JsonIgnoreProperties(ignoreUnknown=true)
class MyPojo {
public static enum Type {
One,
Two,
Three;
#JsonValue
public String value() {
return this.name().toLowerCase();
}
}
private String id;
private Type type;
private String name;
public String getId() {
return this.id;
}
public void setId(String id) {
this.id = id;
}
public Type getType() {
return this.type;
}
public void setType(Type type) {
this.type = type;
}
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
}
There are three things I want to validate:
That all members have values
That the enum value is part of the enumeration
Test some or all values against some criteria (i.e.: min or max length, min or man number value, regular expression, etc)
If the validation fails I want to return a meaningful and readable message.
For the first and third issues I can simply check all the members of the object and see if any are null, and if not test them against the criteria, but this approach can get long and complicated when there are many fields.
As for the 2nd issue, if the value in the input does not match one of the enumeration values then a JsonMappingException is thrown, and so I managed to do this:
try {
MyPojo obj = mapper.readValue(json, MyPojo.class);
}
catch (JsonMappingException e) {
return "invalid value for property: " + e.getPath().get(0).getFieldName();
}
But how do I get the value in the input so that I can return: invalid value: VALUE for property: PROPERTY?
Thanks.
I would recommend using an implementation of JSR-303 Bean Validation. This specification defines a set of annotations and XML config files for specifying constraints that you wish to validation on your domain obects. The reference implementation is available here:
http://www.hibernate.org/subprojects/validator.html

Categories

Resources