I'm making a small web service in Nim, and I need to respond to requests with json. I'm using the jester module to make the service. I expect I can use the json module in Nim's base library to construct some kind of object with fields and values, and then convert it to a json string. But how? Or is there a better way to construct json in Nim?
The marshal module includes a generic object-to-json serialisation algorithm that works for any type (currently, it uses run-time type introspection).
import marshal
type
Person = object
age: int
name: string
var p = Person(age: 38, name: "Torbjørn")
echo($$p)
The output will be:
{"age": 38, "name": "Torbj\u00F8rn"}
In Nim you use the json module to create JsonNode objects which are object variants. These can be constructed with the individual procs like newJObject() and then populate the fields sequence. Another quicker way is to use the %() proc which accepts a sequence of tuples where one value is the string with the json field and the other the individual JsonNode.
Here's an example showing both ways:
import json
type
Person = object ## Our generic person record.
age: int ## The age of the person.
name: string ## The name of the person.
proc `%`(p: Person): JsonNode =
## Quick wrapper around the generic JObject constructor.
result = %[("age", %p.age), ("name", %p.name)]
proc myCustomJson(p: Person): JsonNode =
## Custom method where we replicate manual construction.
result = newJObject()
# Initialize empty sequence with expected field tuples.
var s: seq[tuple[key: string, val: JsonNode]] = #[]
# Add the integer field tuple to the sequence of values.
s.add(("age", newJInt(p.age)))
# Add the string field tuple to the sequence of values.
s.add(("name", newJString(p.name)))
result.fields = s
proc test() =
# Tests making some jsons.
var p: Person
p.age = 24
p.name = "Minah"
echo(%p) # { "age": 24, "name": "Minah"}
p.age = 33
p.name = "Sojin"
echo(%p) # { "age": 33, "name": "Sojin"}
p.age = 40
p.name = "Britney"
echo p.myCustomJson # { "age": 40, "name": "Britney"}
when isMainModule: test()
For anyone else finding the marshal-based answer in this thread. Use this instead:
import json
type
Person = object
age: int
name: string
var p = Person(age: 38, name: "Torbjørn")
echo(%p)
Note that you should not be using marshal for this purpose, it is the equivalent of the pickle module in Python and it may generate JSON that has extra data that you likely don't want. Also, right now it's just a coincidence that it generates JSON, it may choose a different format in the future.
Do the following:
import json
var jsonResponse = %*
{"data": [{ "id": 35,
"type": "car",
"attributes": {"color":"red"} }]}
var body = ""
toUgly(body, jsonResponse)
echo body
Related
I try to parse a Json in groovy/Jenkins(I have no access to readJSON step) and keep the json keys order.
After my research, I found that groovy Map/HashMap objects are not preserving the order of the keys.
The only type which keeping order is LinkedHashMap So I try to convert the output of JsonSlurper.parseText to linkedhashmap but it still changing the items order
def jsonstr = """
{
"Id": 533,
"StartTime": "2022-05-10 11:56:18",
"EndTime": "2022-05-10 11:58:49",
"TimeShift": "N/A",
"Run": "123",
"Setup": "Test",
"Version": "3.17",
"Result": "pass",
"DebugMode": 1,
"NumberOfCores": 3,
}
"""
//init as LinkedHashMap
LinkedHashMap map = new LinkedHashMap()
map = (LinkedHashMap) (new JsonSlurperClassic().parseText(jsonstr))
println(map)
/*
the output is in incorrect order, I expect to get `Id` attribute as a first key but I'm getting:
[EndTime:2022-05-10 11:58:49, Version:3.17, TimeShift:N/A, StartTime:2022-05-10 11:56:18, DebugMode:1, Run:123, NumberOfCores:3, Id:533, Setup:Test, Result:pass]
*/
Here is the solution:
I realized that readJSON step is keeping the order so I try to take a look at its implementation.
readJSON uses net.sf.json.* library, in this library there is an option to parse string to jsonObject (with keeping the order of the keys!) by:
import net.sf.json.JSONSerializer
def map = JSONSerializer.toJSON(jsonstr)
println(map)
NOTES:
if you want use it during a pipeline I suggest you to use readJSON step itself, otherwise, you'll need to approve this function from Manage Jenkins -> script approval
In this method empty properties will be net.sf.json.JSONNull
which holds a textual value "null" -> if (someEmptyKey != null) always returns true (also when null) to avoid that, use:
if (!someEmptyKey instanceof JSONNull )
Sources: docs, jenkins-implementation
We are using Grails version 3.2. I have the json response and Domain object like below. How can we parse json response into domain object using groovy to save in our db without manually converting all the json response elements into my domain properties.
json response
{
"orderId": 1,
"orderDateTime": "07/10/2020",
"orderTypeId": 1,
"userId": "12345",
"StateId": "5"
}
Domain object:
class Order {
Long id
Date orderDate
Long orderType
Long user
Long state
}
Use JsonSlurper
The JsonSlurper class can do this for you.
Simple example
The easiest way is like this:
json = new JsonSlurper().parseText( jsonStrIn )
order = new Order( json )
But that only works if:
Domain class property names match the JSON property names
Every domain property is either a String or has a setter that takes a String argument.
If that's not the case, as it isn't in the sample you provided, then it's a little more complex.
Mapping JSON properties to domain properties
To work around the name differences you can create a map of JSON names to domain names and use the map to modify the JSON string before you parse it.
Map propertyNameMap = [
'orderId': 'id',
'orderDateTime': 'orderDate',
'orderTypeId': 'orderType',
'userId': 'user',
'StateId': 'state'
]
propertyNameMap.each { jsonName, domainName ->
jsonStrIn = jsonStrIn.replaceAll( jsonName, domainName )
}
You might need to use a regular expression, if the JSON property names can occur in the value strings.
Worked example
The following code will populate your domain from the JSON, correctly translating from String to long (where needed) and allowing for differences in property names.
import groovy.json.JsonSlurper
import java.text.SimpleDateFormat
String jsonStrIn = '''{
"orderId": 1,
"orderDateTime": "07/10/2020",
"orderTypeId": 1,
"userId": "12345",
"StateId": "5"
}'''
#groovy.transform.ToString
class Order {
Long id
Date orderDate
Long orderType
Long user
Long state
void setOrderDate( String dt ) {
orderDate = new SimpleDateFormat('dd/MM/yyyy').parse( dt )
}
void setUser( String uid ) {
user = Long.parseLong( uid )
}
}
Map propertyNameMap = [
'orderId': 'id',
'orderDateTime': 'orderDate',
'orderTypeId': 'orderType',
'userId': 'user',
'StateId': 'state'
]
propertyNameMap.each { jsonName, domainName ->
jsonStrIn = jsonStrIn.replaceAll( jsonName, domainName )
}
println jsonStrIn
json = new JsonSlurper().parseText( jsonStrIn )
order = new Order( json )
println order
Code output
Running the above example will display the translated JSON and the Order instance created from it.
{
"id": 1,
"orderDate": "07/10/2020",
"orderType": 1,
"user": "12345",
"state": "5"
}
Order(1, Wed Oct 07 00:00:00 BST 2020, 1, 12345, 53)
[Done] exited with code=0 in 1.677 seconds```
I have a config in json format and I am using Typesafe Config library to load this.
Input config in json format
{
"input": {
"Date": "2014-01-01",
"Ids": ["1","2","3","4"]
}
}
Code
import com.typesafe.config.{Config, ConfigFactory}
val config = ConfigFactory.load("test.json")
val ids = config.getList("input.Ids").unwrapped
# ids: java.util.List[Object] = [1, 2, 3, 4]
All I am getting is list of object. When I try to do a map of each element to int it fails because each element is an object.
ids.map(_.toInt)
<console>:14: error: value toInt is not a member of Object
ids.map(_.toInt)
How to convert the object list to integer list in scala ?
You can use the getStringList method and then map the result to int
config.getStringList("input.Ids").map(_.toInt)
or in this case use the getIntList method directly
I have a json object that I need to update. The original object is a list that looks like this:
[
{
"firstName":"Jane",
"lastName":"Smith"
},
{
"firstName":"Jack",
"lastName":"Brown"
}
]
For each element in the list, we have an extra field, "age", that needs to be added at run-time, so the result should look like the following:
[
{
"firstName":"Jane",
"lastName":"Smith",
"age": "21"
},
{
"firstName":"Jack",
"lastName":"Brown",
"age": "34"
}
]
Any suggestions how to do this so the result is still json?
Thanks.
request.body.asJson.map {
jm => (jm.as[JsObject] ++ Json.obj("age" -> 123))
}
I would recommended deserializing the JSON array you receive into a List of case classes, then having some function fill in the missing attributes based on the current attributes of the case class, and finally serializing them as JSON and serving the response.
Let's make a Person case class with the fields that will be missing as Option:
import play.api.libs.json.Json
case class Person(firstName: String, lastName: String, age: Option[Int])
object Person {
implicit val format: Format[Person] = Json.format[Person]
def addAge(person: Person): Person = {
val age = ... // however you determine the age
person.copy(age = Some(age))
}
}
Within the companion object for Person I've also defined a JSON serializer/deserializer using the format macro, and a stub for a function that will find a person's age then copy it back into the person and return it.
Deep within the web service call you might then have something like this:
val jsArray = ... // The JsValue from somewhere
jsArray.validate[List[Person]].fold(
// Handle the case for invalid incoming JSON
error => InternalServerError("Received invalid JSON response from remote service."),
// Handle a deserialized array of List[Person]
people => {
Ok(
// Serialize as JSON, requires the implicit `format` defined earlier.
Json.toJson(
// Map each Person to themselves, adding the age
people.map(person => Person.addAge(person))
)
)
}
)
This method is much safer, otherwise you'll have to extract values from the array one by one and concatenate objects, which is very awkward. This will also allow you to easily handle errors when the JSON you receive is missing fields you're expecting.
I am writing a Grails/Groovy app and I have a JSON object with a "string" name (grommet and widget) inside the params member that can change. That is, next time it might be acme and zoom. Here is the JSON:
def jx = """{
"job": "42",
"params": {
"grommet": {"name": "x", "data": "y"},
"widget": { "name": "a", "data": "b"}
}
}"""
I am trying to figure out how to get the string grommet . Code so far:
def dalist = new JsonSlurper().parseText(jx)
println dalist.job // Gives: 42
println dalist.params // Gives: [grommet:[name:x, data:y], widget:[name:a, data:b]]
println dalist.params[0] // Gives: null
Any idea how to get the string grommet? Iama going to keep hitting my head against a wall.
The params key on the JSON object is associated with a JSON object, not an array, so you cannot access it by index. JsonSlurper maps JSON objects to Groovy Maps, so you can access params by its keys, which are strings, e.g. dalist.params.grommet, which will give you the map [name: 'x', data: 'y'].
To access the keys on the params you can do dalist.params.keySet(), which will give you the list ['grommet', 'widget']. If you are interested in just knowing params keys, that should do the trick. If you need to get the 'grommet' string for some reason, you can do it by accessing the first element on that list, i.e. dalist.params.keySet()[0], but i don't know why you would want to know that. And i'm not sure if it is guaranteed that the first key of that map will always be 'grommet', as JSON objects are unordered by the spec (from json.org: An object is an unordered set of name/value pairs), but, in turn, Groovy maps are ordered (the default implementation is LinkedHashMap)... so i would assume that the order is preserved when parsing JSON to the Groovy world, but i'd try not to rely on that particular behavior hehe.
It's Map instance, try:
def params = dalist.params.entrySet() as List // entrySet() returns Set, but it's easier to use it as a List
println params
println params.size()
println params[0]
println params[0].key
println params[0].value
This might help you.
import groovy.json.JsonSlurper;
def jx='{"job":"42","params":{"grommet":{"name":"x","data":"y"},"widget":{"name":"a","data":"b"}}}'
def dalist = new JsonSlurper().parseText( jx )
assert dalist.params.getClass().name == "java.util.HashMap";
assert dalist.params.size() == 2;
def keys = dalist.params.collect{ a, b -> a}; // returns "[grommet, widget]"
assert !!dalist.params.get( "grommet" ) == true