I'm trying to convert list of objects to JSON, but it won't serialise it properly
import marshal
type
DocItem = object of RootObj
tags: seq[string]
TextDoc = object of DocItem
text: string
TodoDoc = object of DocItem
todo: string
var docs: seq[ref DocItem]
proc to_ref[T](o: T): ref T =
result.new
result[] = o
docs.add TextDoc(text: "some doc", tags: #["help"]).to_ref
docs.add TodoDoc(todo: "some todo").to_ref
echo $$docs
The output is:
[[4312834120, {"tags": ["help"]}], [4312834280, {"tags": []}]]
While I need it to be
[{"text": "some doc", "tags": ["help"]}, {"todo": "some todo", "tags": []}]
Also, is there a way to configure marshal to output pretty JSON?
Unfortunately the marshaling seems correct: it shows the pointer to the ref Object. Maybe you can resort to composition instead of inheritance:
import marshal
type
Todo = object of RootObj
todo: string
Text = object of RootObj
text: string
DocItem = object of RootObj
tags: seq[string]
text: Text
todo: Todo
var docs: seq[DocItem] = #[]
docs.add DocItem(tags: #["help"], todo: Todo(todo: "some todo"))
docs.add DocItem(tags: #["help"], text: Text(text: "some text"))
echo $$docs
Or maybe you can try object variants. I'm adding a new imaginary "Link" type to illustrate it better:
import marshal
type
Content = enum
tTodo
tText
tImage
DocItem = object of RootObj
tags: seq[string]
case kind: Content
of tTodo, tText:
text: string
of tLink:
url: string
var docs: seq[DocItem] = #[]
docs.add DocItem(tags: #["help"], kind: tTodo, todoVal: "some todo")
docs.add DocItem(tags: #["help"], kind: tText, textVal: "some text")
docs.add DocItem(tags: #["help"], kind: tLink, url: "http://stackoverflow.com")
echo $$docs
Related
So I have a GraphQL API which I wrote a query to and it works and sent me back data in GraphiQL.
query dishes {
dishes {
name
recipeNote
description
}
}
This is my Graphene translation of what I have
class Dish(ObjectType):
name = String()
recipeNote = String()
description = String()
class Query(ObjectType):
first = Field(Dish)
def resolve_first(parent, info):
return parent
query_string = "{ first { dishes { name recipeNote description } } }"
result = schema.execute(
query_string)
print(result)
print(result.data)
However this gives me an error {'errors': [{'message': 'Cannot query field "dishes" on type "Dish".', 'locations': [{'line': 1, 'column': 11}]}]}
from graphene import List, ObjectType, String
class Dish(ObjectType):
name = String()
recipeNote = String()
description = String()
class Query(ObjectType):
dishes = List(Dish)
def resolve_dishes(parent, info):
return [Dish(name='', recipeNote='', description=''), ...]
query_string = "query dishes {
dishes {
name
recipeNote
description
}
}"
result = schema.execute(
query_string)
print(result)
print(result.data)
I want to access a JSON HasMap via String like data['dimension1']['dimension2'] but i want to do that dynamically like data[myAccessor].
Code
import groovy.json.JsonSlurperClassic
dataContent = '''
{
"Test": {
"Info": "Hello"
}
}
'''
def jsonSlurper = new JsonSlurperClassic()
data = jsonSlurper.parseText(dataContent)
println data['Test']['Info'] // Prints 'Hello'
accessor = "'Test']['Info'"
println data[accessor] // Prints 'null'
'''
Yeah, you can't just execute complex text as code like that 😕 (or 😄, I can't decide)
The best you can do is to have accessor be something splittable like
accessor = 'Test->Info'
And then you can split this and "walk" down the input structure to get the result you're after like so:
import groovy.json.JsonSlurperClassic
dataContent = '''
{
"Test": {
"Info": "Hello"
}
}
'''
def jsonSlurper = new JsonSlurperClassic()
data = jsonSlurper.parseText(dataContent)
accessor = "Test->Info"
println accessor.split('->').inject(data) { currentData, accessorPart ->
currentData?.getAt(accessorPart)
}
You can leverage Groovy's Eval and represent the accessor with property notation to have something closer to your intent:
import groovy.json.JsonSlurperClassic
dataContent = '''
{
"Test": {
"Info": "Hello"
}
}
'''
def jsonSlurper = new JsonSlurperClassic()
data = jsonSlurper.parseText(dataContent)
println data.'Test'.'Info' // prints 'Hello'
accessor = "'Test'.'Info'"
Eval.x(data, "println x.${accessor}") // prints 'Hello'
I have JSON looking like:
{
"days": [
{
"mintemp": "21.8"
}
]
}
With Groovy, I parse it like this:
class WeatherRow {
String mintemp
}
def file = new File("data.json")
def slurper = new JsonSlurper().parse(file)
def days = slurper.days
def firstRow = days[0] as WeatherRow
println firstRow.mintemp
But actually, I would like to name my instance variable something like minTemp (or even something completely random, like numberOfPonies). Is there a way in Groovy to map a member of a map passed to a constructor to something else?
To clarify, I was looking for something along the lines of #XmlElement(name="mintemp"), but could not easily find it:
class WeatherRow {
#Element(name="mintemp")
String minTemp
}
Create a constructor that takes a map.
Runnable example:
import groovy.json.JsonSlurper
def testJsonStr = '''
{"days": [
{ "mintemp": "21.8" }
]}'''
class WeatherRow {
String minTemp
WeatherRow(map) {
println "Got called with constructor that takes a map: $map"
minTemp = map.mintemp
}
}
def slurper = new JsonSlurper().parseText(testJsonStr)
def days = slurper.days
def firstRow = days[0] as WeatherRow
println firstRow.minTemp
Result:
Got called with constructor that takes a map: [mintemp:21.8]
21.8
(of course you'd remove the println line, it's just there for the demo)
You can achieve this using annotation and simple custom annotation processor like this:
1. Create a Custom Annotation Class
#Retention(RetentionPolicy.RUNTIME)
#interface JsonDeserializer {
String[] names() default []
}
2. Annotate your instance fields with the custom annotation
class WeatherRow{
#JsonDeserializer(names = ["mintemp"])
String mintemp;
#JsonDeserializer(names = ["mintemp"])
String minTemp;
#JsonDeserializer(names = ["mintemp"])
String numberOfPonies;
}
3. Add custom json deserializer method using annotation processing:
static WeatherRow fromJson(def jsonObject){
WeatherRow weatherRow = new WeatherRow();
try{
weatherRow = new WeatherRow(jsonObject);
}catch(MissingPropertyException ex){
//swallow missing property exception.
}
WeatherRow.class.getDeclaredFields().each{
def jsonDeserializer = it.getDeclaredAnnotations()?.find{it.annotationType() == JsonDeserializer}
def fieldNames = [];
fieldNames << it.name;
if(jsonDeserializer){
fieldNames.addAll(jsonDeserializer.names());
fieldNames.each{i ->
if(jsonObject."$i")//TODO: if field type is not String type custom parsing here.
weatherRow."${it.name}" = jsonObject."$i";
}
}
};
return weatherRow;
}
Example:
def testJsonStr = '''
{
"days": [
{
"mintemp": "21.8"
}
]
}'''
def parsedWeatherRows = new JsonSlurper().parseText(testJsonStr);
assert WeatherRow.fromJson(parsedWeatherRows.days[0]).mintemp == "21.8"
assert WeatherRow.fromJson(parsedWeatherRows.days[0]).minTemp == "21.8"
assert WeatherRow.fromJson(parsedWeatherRows.days[0]).numberOfPonies == "21.8"
Check the full working code at groovyConsole.
I am using the Groovy MarkupBuilder to create some XML.
In my scenario I get an xml string which is essentially the body of the xml document and then I want to surround it with some other stuff.
Something like this....
def xmltext = '''<node><name short="yes">tim</name><fun>maybe</fun></node>'''
def body = new XmlSlurper(false,false).parseText( xmltext )
def writer = new StringWriter()
def xml = new MarkupBuilder(writer)
xml.mkp.xmlDeclaration version: '1.0', encoding: 'UTF-8'
xml.'cre:InputParamters'('xmlns:cre': 'http://xmlns.oracle.com/apps/ar/soaprovider/plsql/ar_invoice_api_pub/create_single_invoice/') {
xml.'cre:P_P_TRX_HEADER_TBL' {
xml.'cre:P_TRX_HEADER_TBL_ITEM' {
map 'cre:TRX_HEADER_ID', '',xml
map 'cre:TRX_DATE', requestPayload?.invoiceDate, xml
map 'cre:TRX_CURRENCY', requestPayload?.currencyCode, xml
map 'cre:TRX_CLASS', 'INV', xml
map 'cre:CUST_TRX_TYPE_ID', '1034', xml
map 'cre:BILL_TO_CUSTOMER_ID', '147055', xml
}
}
<<APPEND ELEMENTS FROM XML STRING HERE>>
}
private map (Object field, Object value, MarkupBuilder xml) {
if (value) {
xml."$field"(value)
}
}
return writer.toString()
Could someone help me with the bit 'APPEND ELEMENTS FROM XML STRING HERE' and how to get that the work for me?
thanks
Here is the changed script:
Note that you have used some variable to get the values. To demonstrate I have used fixed values below which you may be able to replace it.
import groovy.xml.*
def xmltext = '''<node><name short="yes">tim</name><fun>maybe</fun></node>'''
def builder = new StreamingMarkupBuilder()
builder.encoding = 'UTF-8'
def xml = builder.bind {
mkp.xmlDeclaration()
namespaces << [cre:'http://xmlns.oracle.com/apps/ar/soaprovider/plsql/ar_invoice_api_pub/create_single_invoice/']
cre.InputParamters{
cre.P_P_TRX_HEADER_TBL {
cre.P_TRX_HEADER_TBL_ITEM {
cre.TRX_HEADER_ID ('')
cre.TRX_DATE ('2017-02-17')
cre.TRX_CURRENCY( 'USD')
cre.TRX_CLASS('INV')
cre.CUST_TRX_TYPE_ID( '1034')
cre.BILL_TO_CUSTOMER_ID( '147055')
}
}
mkp.yieldUnescaped xmltext
}
}
println XmlUtil.serialize(xml)
You may try it quickly online Demo
Output
Instead of having to declare all the properties in a map from an object like:
prop1: object.prop1
Can't you just drop the object in there like below somehow? Or what would be a proper way to achieve this?
results: [
object,
values: [
test: 'subject'
]
]
object.properties will give you a class as well
You should be able to do:
Given your POGO object:
class User {
String name
String email
}
def object = new User(name:'tim', email:'tim#tim.com')
Write a method to inspect the class and pull the non-synthetic properties from it:
def extractProperties(obj) {
obj.getClass()
.declaredFields
.findAll { !it.synthetic }
.collectEntries { field ->
[field.name, obj."$field.name"]
}
}
Then, map spread that into your result map:
def result = [
value: true,
*:extractProperties(object)
]
To give you:
['value':true, 'name':'tim', 'email':'tim#tim.com']
If you don't mind using a few libraries here's an option where you convert the object to json and then parse it back out as a map. I added mine to a baseObject which in your case object would extend.
class BaseObject {
Map asMap() {
def jsonSlurper = new groovy.json.JsonSlurperClassic()
Map map = jsonSlurper.parseText(this.asJson())
return map
}
String asJson(){
def jsonOutput = new groovy.json.JsonOutput()
String json = jsonOutput.toJson(this)
return json
}
}
Also wrote it without the json library originally. This is like the other answers but handles cases where the object property is a List.
class BaseObject {
Map asMap() {
Map map = objectToMap(this)
return map
}
def objectToMap(object){
Map map = [:]
for(item in object.class.declaredFields){
if(!item.synthetic){
if (object."$item.name".hasProperty('length')){
map."$item.name" = objectListToMap(object."$item.name")
}else if (object."$item.name".respondsTo('asMap')){
map << [ (item.name):object."$item.name"?.asMap() ]
} else{
map << [ (item.name):object."$item.name" ]
}
}
}
return map
}
def objectListToMap(objectList){
List list = []
for(item in objectList){
if (item.hasProperty('length')){
list << objectListToMap(item)
}else {
list << objectToMap(item)
}
}
return list
}
}
This seems to work well
*:object.properties