I'm having a tree of domain classes that i want to convert to JSON via a deep converter:
import grails.converters.deep.JSON
deepObject as JSON
Somewhere in the tree I'm having Double.NaN values in some fields and the JSON parser throws an exception:
org.apache.commons.lang.UnhandledException: org.codehaus.groovy.grails.web.converters.exceptions.ConverterException: org.codehaus.groovy.grails.web.json.JSONException: JSON does not allow non-finite numbers
How can I handle that case? May be returning a string ('NaN').
I tried repacing the JSONObject.testValidity(Object o) method, but this is a pojo and so it does not work.
Edit
I also tried to register a marshaller in Bootstrap.groovy:
JSON.registerObjectMarshaller(Double) {
return it == Double.NaN ? 'NaN' : it.toString()
}
But it also wont work.
Unfortunately most native values rendering is hardcoded in grails.converters.JSON, and can't be personalized with registerObjectMarshaller. One solution is to specify the JSON converter class and override its value(Object o) method as:
import grails.converters.JSON
class MyJSONConverter extends JSON {
#Override
public void value(Object o) throws ConverterException {
if (o instanceof Double && o.NaN)
o = 'Nan'
super.value(o);
}
}
and call your custom implementation with
import org.grails.web.converters.ConverterUtil
import org.grails.web.servlet.mvc.GrailsWebRequest
MyJSONConverter myJSONConverter = ConverterUtil.createConverter( MyJSONConverter.class, deepObject, GrailsWebRequest.lookup()?.applicationContext);
String deepString = myJSONConverter as String;
Related
I am using my properties file to get the values for #Scheduled annotation attributes.I am able to get the values from properties file but when I tries to pass String constant reference to Annotation attribute then compile time exception is raised.
#Slf4j
#CompileStatic
class TestJobService {
static lazyInit = false
public static String jobInterval = getSomePropertiesFileValues?.fixedRateInMS instanceof String? getSomePropertiesFileValues.fixedRateInMS:'10000'
#Scheduled(fixedDelayString = TestJobService.jobInterval)
void executeEveryTenSeconds() {
def date = new Date()
println date.format('yyyy/MM/dd HH:mm', TimeZone.getTimeZone('IST'))
}
}
Attribute 'fixedDelayString' should have type 'java.lang.String'; but
found type 'java.lang.Object' in
#org.springframework.scheduling.annotation.Scheduled
Then I tried to use String to pass like:
#Slf4j
#CompileStatic
class TestJobService {
static lazyInit = false
#Scheduled(fixedDelayString = '${getSomePropertiesFileValues.fixedRateInMS}')
void executeEveryTenSeconds() {
def date = new Date()
println date.format('yyyy/MM/dd HH:mm', TimeZone.getTimeZone('IST'))
}
}
OR
public static final String jobInterval = getSomePropertiesFileValues?.fixedRateInMS instanceof String? getSomePropertiesFileValues.fixedRateInMS:'10000'
prevents the variable from being treated as an inline constant and compiler complains of not being inline constant.
I understand that using single quote '${getSomePropertiesFileValues.fixedRateInMS}'we can get compiler to know that I want GString behaviour. But I don't know is this a bug in Groovy or its a functionality which I need to implement in some other way to pass the string values as annotation attributes. Any lead or any help is highly appreciable.
I am trying to implement custom function using Saxon as defined here-> https://specifications.xbrl.org/registries/functions-registry-1.0/80132%20xfi.identifier/80132%20xfi.identifier%20function.html
public class IdentifierFunction implements ExtensionFunction {
public QName getName() {
return new QName("http://www.xbrl.org/2005/function/instance", "identifier");
}
public SequenceType getResultType() {
return SequenceType.makeSequenceType(ItemType.STRING, OccurrenceIndicator.ONE);
}
public net.sf.saxon.s9api.SequenceType[] getArgumentTypes() {
return new SequenceType[] { SequenceType.makeSequenceType(ItemType.STRING, OccurrenceIndicator.ONE) };
}
public XdmValue call(XdmValue[] arguments) throws SaxonApiException {
String arg = ((XdmAtomicValue) arguments[0].itemAt(0)).getStringValue();
String newExpression="(//xbrli:xbrl/xbrli:context[#id=("+arg+"/#contextRef"+")])[1]/xbrli:entity/xbrli:identifier";
String nodeString=this.getxPathResolver().resolveNode(this.getXbrl(),newExpression);
return new XdmAtomicValue(nodeString);
}
}
resolveNode() is above code is implemented as follows
public String resolveNode(byte[] xbrlBytes, String expressionValue) {
// 1. Instantiate an XPathFactory.
javax.xml.xpath.XPathFactory factory = new XPathFactoryImpl();
// 2. Use the XPathFactory to create a new XPath object
javax.xml.xpath.XPath xpath = factory.newXPath();
NamespaceContext ctx = new NamespaceContext() {
#Override
public String getNamespaceURI(String aPrefix) {
if (aPrefix.equals("xfi"))
return "http://www.xbrl.org/2005/function/instance";
else if (aPrefix.equals("xs"))
return "http://www.w3.org/2001/XMLSchema";
else if (aPrefix.equals("xbrli"))
return "http://www.xbrl.org/2003/instance";
else
return null;
}
#Override
public Iterator getPrefixes(String val) {
throw new UnsupportedOperationException();
}
#Override
public String getPrefix(String uri) {
throw new UnsupportedOperationException();
}
};
xpath.setNamespaceContext(ctx);
try {
DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder documentBuilder = builderFactory.newDocumentBuilder();
Document someXML = documentBuilder.parse(new InputSource(new StringReader(new String(xbrlBytes))));
// 3. Compile an XPath string into an XPathExpression
javax.xml.xpath.XPathExpression expression = xpath.compile(expressionValue);
Object result = expression.evaluate(someXML, XPathConstants.NODE);
// 4. Evaluate the XPath expression on an input document
Node nodes = (Node) result;
return nodeToString(nodes);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
When I evaluate xfi:identifier(args) , i get String like below:
<xbrli:identifier xmlns:xbrli="http://www.xbrl.org/2003/instance"
xmlns:iso4217="http://www.xbrl.org/2003/iso4217"
xmlns:jenv-bw2-dim="http://www.nltaxonomie.nl/nt13/jenv/20181212/dictionary/jenv-bw2-axes"
xmlns:jenv-bw2-dm="http://www.nltaxonomie.nl/nt13/jenv/20181212/dictionary/jenv-bw2-domains"
xmlns:jenv-bw2-i="http://www.nltaxonomie.nl/nt13/jenv/20181212/dictionary/jenv-bw2-data"
xmlns:kvk-i="http://www.nltaxonomie.nl/nt13/kvk/20181212/dictionary/kvk-data"
xmlns:link="http://www.xbrl.org/2003/linkbase"
xmlns:nl-cd="http://www.nltaxonomie.nl/nt13/sbr/20180301/dictionary/nl-common-data"
xmlns:rj-i="http://www.nltaxonomie.nl/nt13/rj/20181212/dictionary/rj-data"
xmlns:rj-t="http://www.nltaxonomie.nl/nt13/rj/20181212/dictionary/rj-tuples"
xmlns:xbrldi="http://xbrl.org/2006/xbrldi"
xmlns:xlink="http://www.w3.org/1999/xlink"
scheme="http://www.kvk.nl/kvk-id">62394207</xbrli:identifier>
However, I want to evaluate function number(xfi:identifier(args))
This results in NaN which is obvious because complete node string cannot be converted to number. I think, I need to change my function so that it returns Node. However, I am not sure how to do that. I tried google and also looked at Saxon documentation, but no luck yet.
Can someone help me? Basically, custom function should return an element node as per definition. and when I use number(xfi:identifier) it should give me 62394207 in this case.
regards,
Venky
Firstly, the XBRL spec for the function seems to imply that the function expects a node as argument and returns a node as its result, but in your implementation getArgumentTypes() and getResultType() define the type as xs:string - so this needs to change.
And the function should return an XdmNode, which is a subclass of XdmValue.
Next, it's very inefficient to be creating a DocumentBuilderFactory and XPathFactory, constructing an XML document tree, and compiling an XPath expression, every time your function is executed. I strongly suspect none of this is necessary.
Instead of having this.getXbrl() return a raw lexical document as byte[], have it return a prebuilt XdmNode representing the document tree. And then I would suggest that rather than selecting within that tree using XPath, you use Saxon's linq-like navigation API. If this XdmNode is in variable "root", then the XPath expression
//xbrli:xbrl/xbrli:context[#id=("+arg+"/#contextRef"+")
translates into something like
root.select(descendant("xbrl").then(child("context)).where(attributeEq("id", arg))
(except that I'm not quite sure what you're passing as arg to make your XPath expression make sense).
But you can use XPath if you prefer; just use Saxon's s9api interfaces for XPath and make sure the XPath expression is only compiled once and used repeatedly. It's straightforward then to get an XdmNode as the result of your XPath expression, which can be returned directly as the result of your extension function.
I want to add a custom xpath extension function to the Saxon-HE transformer. This custom function should accept one or more arguments. Let's use the string concatenation analogy for concatenating one or more string arguments. Following the sample on the saxon page, i wrote the following code:
ExtensionFunction myconcat = new ExtensionFunction() {
public QName getName() {
return new QName("http://mycompany.com/", "myconcat");
}
public SequenceType getResultType() {
return SequenceType.makeSequenceType(
ItemType.STRING, OccurrenceIndicator.ONE
);
}
public net.sf.saxon.s9api.SequenceType[] getArgumentTypes() {
return new SequenceType[]{
SequenceType.makeSequenceType(
ItemType.STRING, OccurrenceIndicator.ONE_OR_MORE)};
}
public XdmValue call(XdmValue[] arguments) throws SaxonApiException {
//concatenate the strings here....
String result = "concatenated string";
return new XdmAtomicValue(result);
}
};
i have expected that the following xpath expression would work in an xsl file
<xsl:value-of select="myconcat('a','b','c','...')">
Unfortunately i got the following exception:
XPST0017: Function myconcat must have 1 argument
What is the right way of creating a custom function for this use case?
Thanks.
The standard mechanisms for creating extension functions don't allow a variable number of arguments (it's not really pukka to have such functions in the XPath view of the world - concat() is very much an exception).
You can do it by creating your own implementation of the class FunctionLibrary and adding your FunctionLibrary to the static context of the XSLT engine - but you're deep into Saxon internals if you attempt that, so be prepared for a rough ride.
I have a case class called Recording that I can serialize correctly using spray-json, but I can't serialize a List[Recording].
The answers I've seen about List serialization involve missing imports of DefaultJsonProtocol._ but that hasn't helped me here.
Here's the code:
import spray.json._
import scala.collection.immutable
object RecordingJsonProtocol extends DefaultJsonProtocol {
implicit val recordingFormat = jsonFormat2(Recording.apply)
}
case class Recording(name: String, hashOffsetIndex: immutable.Map[String, Int])
object RecordingLoader {
import RecordingJsonProtocol._
import DefaultJsonProtocol._
def recordingsToJson(filename: String, recordings : List[Recording]) = {
println(recordings.toJson.prettyPrint)
}
}
The error I receive is :
Error:(16, 24) Cannot find JsonWriter or JsonFormat type class for List[Recording]
println(recordings.toJson.prettyPrint)
^
Edit: problem solved
import DefaultJsonProtocol._
is redundant because RecordingJsonProtocol extends DefaultJsonProtocol -- but it's not merely redundant, it also prevents RecordingJsonProtocol from working.
I initially had my #NodeEntity class having a #GraphProperty of java.util.Date type. The graph repository APIs automatically convert that to long / string value and stores it based on my property type.
As I wanted to import huge chunk of data from csv, I wanted this date field in my custom or pre-defined string format, say "yyyyMMdd". So I wrote my spring converter factories for both String -> Date and DAte -> String and registered them in the spring xml for conversion service. But unfortunately my converter was never called. Only the standard converter was called.
Later, when I wrap the date object inside MyDate class and changed the converter to String -> MyDAte and vice versa instead of java.util.Date, converter was called and the required serialization was done.
I dont want this MyDate wrapper around Date just for the sake of converter. Now, my question is, how to override the default date converter with my converters?
try this:
#Configuration
#EnableNeo4jRepositories
static class TestConfig extends Neo4jConfiguration {
#Bean
GraphDatabaseService graphDatabaseService() {
return new ImpermanentGraphDatabase();
}
#Bean
protected ConversionService neo4jConversionService() throws Exception {
ConversionService conversionService = super.neo4jConversionService();
ConverterRegistry registry = (ConverterRegistry) conversionService;
registry.removeConvertible(Date.class, String.class);
registry.removeConvertible(String.class, Date.class);
//add your own converters like this
registry.addConverter(new MyDateToStringConverter());
registry.addConverter(new MyStringToDateConverter());
return conversionService;
}
}