Why does tWriteJSONField create an array for null values? - json

I have joined Countries to Locations in the HR Sample database in OracleXE.
I am then using a tMap to generate a nested JSON document.
it works, but for some reason null values in the Location are coming through as arrays in the final output in the console (have also tried MongoDB).

Because tWriteJSONField generates an xml, then converts it to JSON using json-lib. Your null value will be converted to an empty xml node <STATE_PROVINCE/>, and json-lib, having no context of this node, will assume it is a parent node with no children, instead of an empty text (null notion is already far at this point).
Here is what happens in short:
package test.json;
public class JSONTest {
public static void main(String[] args) {
net.sf.json.xml.XMLSerializer s = new net.sf.json.xml.XMLSerializer();
s.clearNamespaces();
s.setSkipNamespaces(true);
s.setForceTopLevelObject(true);
net.sf.json.JSON json = s.read("<?xml version=\"1.0\" encoding=\"ISO-8859-15\"?>" +
"<org>" +
"<STATE_PROVINCE/>" +
"</org>"
);
System.out.println(json.toString());
}
}
Result:
{"org":{"STATE_PROVINCE":[]}}
A dirty solution is to use attributes instead of nodes in your tWriteJSONField, but it will prefix your properties with #. So after this component you put a tReplace, search "\"#", replace with "\"", uncheck whole word, check global expression. Your final JSON will have no property if null.

Thanks to https://www.talendforge.org/forum/viewtopic.php?id=27791
Insert a tJavaRow with the following code right after your tWriteJsonField:
output_row.output = input_row.output.replaceAll(",?\"[a-zA-Z_0-9]*\":\\[\\]", "");

I believe the elegant (and short) solution would be the following.
Talend docs state that:
When configuring a JSON tree, the default type of an element is
string. If an element is not of type string, you need to add an
attribute for the element to set its type.
So, to the object receiving a null value, you should add an attribute named class and set its static value to object.
Pic: JSON Tree Configuration
And voilĂ !
PIC: "Complemento":null

Related

Default values for unknown elements in mORMot / Synopse JSON

I really love the way mORMot / Synopse has implemented the handling of JSON, especially the way you can use the JSON elements in your code (like MyString := myjson.name). This is very intuitive and useful in wrapping objects which only have one Variant (JSON) with state which we access through getters/setters, like below:
TMyObject = class
private
FState: Variant;
function GetName: String;
public
constructor Create(AJson: Variant);
property Name: String read GetName;
end;
function TMyObject.GetName: String;
begin
Result := FState.name;
end;
It is really powerful, but I would like to get the 'default' variant value if an element is not found in the corresponding JSON document (so, above example should return an empty string if 'name' is not present).
I don't want to use NullStrictConvert, because that is not thread safe and influences the rest of our program. Of course I could check VarIsNull(FState.name), but then I have to do this for each element and I prefer not to have this extra boilerplate.
Any suggestions?
When you unserialize some JSON into an object, the missing fields are left untouched IIRC.
So you can just set the fields to their default values before unserializing the JSON.
One way of doing it is to inherit from TSynPersistent and override the Create constructor and set default value(s).
Edit: You may use a TDocVariantData instead of variant, and call GetAsRawUTF8() and such methods which return false if the property is not existing.

Saving Document directly as a value of id in Spring-data-Couchbase

I want to save a JSON object as a Document in Couchbase. The id of this document is supposed to be retrieved from this JSON object and the value is supposed to be this JSON object itself. Since this JSON is too complex, I haven't mapped it directly to any POJO class, but I have created a Simple POJO, which has two fields as shown below
#Document
public class SimplePojo{
#Id
private String id;
#Field()
private String complexJsonString;//the JSON string is stored in this variable
}
I also have a SimplePojoRepository as shown below
#Component
public interface SimplePojoRepository extends CouchbaseRepository<SimplePojo, String>{
}
Now, I am setting the id and complexJsonString manually before calling the save method:-
SimplePojo myObj= new SimplePojo();
myObj.setId(myKey);
myObj.setComplexJsonString(jsonString);
simplePojoRepository.save(myObj);
This is working fine, but it is saving the Document in below format
myKey: {
complexJsonString : {//the original json Object here}
}
but I don't want this, I want to save it like this:-
myKey : {//the original json Object here}
So, to make it clear, I don't want to save my JSON object as a value of complexJsonString but rather, directly as a value of the myKey . Can someone please guide me on how to achieve this?
If you want to store the complexJsonString as a nested entity within your main object, you have to transform it in a Pojo:
myObj.setSomeEntity(new SomeEntity())
You can easily transform your JSON-encoded String to object using jackson's ObjectMapper:
ObjectMapper mapper = new ObjectMapper();
mapper.readValue( jsonString, SomeEntity.class);
However, if you don't have control on the structure of this json, you will need to use the standard Java SDK instead of the Spring Data One:
JsonObject obj = JsonObject.create().put(this.documentTypeName, this.documentValue)
.put("attrNam1", "attrValue1")
.put("attrNam2", "attrValue2")
JsonDocument doc = JsonDocument.create(session.getId(), maxExpirationTime, obj);
bucket.upsert(doc)
In the case above, you will need to parse your JSON-encoded string using some lib (ex: gson/jackson) and then convert it to a couchbase JsonDocument.
Lastly, you could also leave your code as it is and use the N1QL function DECODE_JSON() whenever you need to access some property of this json string.
ex:
SELECT
i.itemName as itemName,
SUM(i.quantity) AS totalQuantity
FROM sessionstore s
UNNEST DECODE_JSON(s.sessionCart).shoppingCart.items i
WHERE s.sessionCart IS NOT MISSING
GROUP BY i.itemName
ORDER BY SUM(i.quantity) DESC
LIMIT 10

How to add extra fields to an Object during Jackson's json serialization?

I need to add new property to an object, when serializing to JSON. The value for the property is calculated on runtime and does not exist in the object. Also the same object can be used for creation of different JSON with different set ot fields (kind of having a base class with subclasses, but I don't want to create ones just for JSON generation).
What is the best way of doing that, which doesn't involve creation of custom serializer class, which will take care of serializing of whole set of object's fields? Or may be it is possible to inherit some "basic" serializer, and simply take it's output and add new field to it somehow?
I learned about mixins, and looks like it is possible to rename/hide some fields, however it seems not be possible to add an extra one.
Can you not just add a method in value class? Note that it does not have to be either public, or use getter naming convention; you could do something like:
public class MyStuff {
// ... the usual fields, getters and/or setters
#JsonProperty("sum") // or whatever name you need in JSON
private int calculateSumForJSON() {
return 42; // calculate somehow
}
}
Otherwise you could convert POJO into JSON Tree value:
JsonNode tree = mapper.valueToTree(value);
and then modify it by adding properties etc.
2021 calling...
Simplest way I found to do this is #JsonUnwrapped:
public class Envelope<T> {
#JsonUnwrapped // content's fields are promoted alongside the envelope's
public T content;
// Transmission specific fields
public String url;
public long timestamp;
}
This works (bi-directionally) so long as Envelope's fieldnames do not clash with those of content. Also has a nice feature of keeping the transmission properties at the end of the serialised JSON.
One option is to add a field for this property and set it on the object before writing to JSON. A second option, if the property can be computed from other object properties you could just add a getter for it, for example:
public String getFullName() {
return getFirstName() + " " + getLastName();
}
And even though there's no matching field Jackson will automatically call this getter while writing the JSON and it will appear as fullName in the JSON output. If that won't work a third option is to convert the object to a map and then manipulate it however you need:
ObjectMapper mapper //.....
MyObject o //.....
long specialValue //.....
Map<String, Object> map = mapper.convertValue(o, new TypeReference<Map<String, Object>>() { });
map.put("specialValue", specialValue);
You're question didn't mention unmarshalling but if you need to do that as well then the first option would work fine but the second two would need some tweaking.
And as for writing different fields of the same object it sounds like a job for #JsonView

grails JSON binding to LinkedHashSet instead of JSONArray for deeply nested relation

I have three levels deep of a hierarchy that I am binding in a JSON request:
Group -> Zone -> Segment
(1) -> (n) -> (n)
In my command object I have:
class GroupCommand {
Long id
Set zones
}
When binding the JSON request the zones get bound properly and I get a LinkedHashSet that I can get the properties of and use with my domain object. However when I get to iterating over the segments in my service:
groupCommand.zones.each { zone ->
zone.segments.each { segment ->
//Would like to get LinkedHashMap here also
//but get JSONArray
}
}
As noted above, I'd ideally like the deeply nested Segments to also bind to a LinkedHashMap but it's bound to a JSONArray.
Any suggestions how to get it bound to a LinkedHashMap as I'd like to avoid having to manipulate JSONArray in my service and thereby coupling my service with the JSON format.
If there's a way to do the conversion at the command level using a getter I'm all for that also.
thanks
EDIT:
Using
List zones = org.apache.commons.collections.list.LazyList.decorate(new ArrayList(), new org.apache.commons.collections.functors.InstantiateFactory(ZoneCommand.class))
appears to work but the underlying objects are still JSON elements. I then tried using:
List<RateZoneCommand> zones = org.apache.commons.collections.list.LazyList.decorate(new ArrayList(), new org.apache.commons.collections.functors.InstantiateFactory(ZoneCommand.class))
and at least I got an error indicating it trying to convert:
Validation error: ... org.codehaus.groovy.grails.web.json.JSONArray to required type java.util.List for property zones; nested exception is java.lang.IllegalStateException: Cannot convert value of type [org..JSONObject] to required type [ZoneCommand] for property zones[0]: no matching editors or conversion strategy found.
Create a command class for each level. Mark Zone- and Segment-command as #Validateable.
To your GroupCommand, add:
List zones = org.apache.commons.collections.list.LazyList.decorate(new ArrayList(), new org.apache.commons.collections.functors.InstantiateFactory(ZoneCommand.class))
To your ZoneCommand, add:
List segments = org.apache.commons.collections.list.LazyList.decorate(new ArrayList(), new org.apache.commons.collections.functors.InstantiateFactory(SegmentCommand.class))
In your form just use group.zones[0].segments[0]. If you change a field type of your command class, remember to restart the grails server.

how to get a real null value instead of a JSONObject.NULL value when parsing JSON in grails

I'm trying to parse some JSON in Grails using the grails.converters.JSON library. I have a field which will contain either a string, or a null value. When I parse the JSON and get the field, the null values come back as a JSONObject.NULL type. This is not good when checking != null as JSONObject.NULL is evaluated as non-null (not good for null checks)
def obj = JSON.parse('{"date1":null,"date2":"2011-06-26T05:00:00Z"}')
def date1 = obj.date1
if (date1)
parse(date1) // parse error occurs here because date1 evaluates true in the if because it is JSONObject.NULL
Is there an easy way to get the parse to parse a real null value so that I don't have to check if the object is a JSONObject.NULL.
I tried the suggestion here to use .toString(), but it ended up returning the string value 'null' instead of actual null for a JSONObject.NULL value.
You may find this more useful and natural
JSONObject.NULL.equals(jsonObj.get("key_name"))
Have a look at: http://grails.1312388.n4.nabble.com/The-groovy-truth-of-JSONObject-Null-td3661040.html
Ian Roberts mentions a nice trick to make a null check possible:
JSONObject.NULL.metaClass.asBoolean = {-> false}
I think I found a better solution, which consists in overriding the toString() method implementation of the JSONObject.NULL inner class by copying the JSONObject.java file into your Grails src/java project and then changing the implementation to this:
/**
* Get the "" string value.
* #return An empty String "".
*/
#Override
public String toString() {
return "";
}
Once you restart with this new class in your classpath, the classloader will use your JSONObject class instead of the one packaged in the Grails dependencies.
Make sure you keep it in the same package as the original.
For more details you can go here: https://github.com/grails/grails-core/issues/9129
Hope it helps :-)