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```
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
I call serializer:
serializer = MySerializer(qs, many=True) when qs - QuerySets for myModel
from rest_framework.serializers import Serializer
class MySerializer(Serializer):
param1 = CharField()
param2 = IntegerField(required=False)
custom_fields = JSONField()
class Meta:
pass
Next, I just use the custom_fields and get the values manually.
Is it possible at this stage to get the fields inside this custom_fields and return them through the serializer?
custom_fields contains:
{
'custom_value1': 3,
'custom_value2': 5
}
updated: What i want to get after serializer:
{
'param1': 'value1',
'param2': 'value2',
'custom_value1': 3,
'custom_value2': 5
}
I'm afraid I exactly understand your question... You're asking that you can GET your custom field's data?
Then answer is Yes
If you just call GET request to APIView using MySerializer, you can get all data from your model.
If you want to get data from your Model, you simply using ModelSerializer. (http://www.django-rest-framework.org/api-guide/serializers/#modelserializer). ModelSerializer is much easier but you can use your own serializer, too.
Nowadays I experienced similar situation, and I can get all my extra data (same as you, it's json object)
I attach my serializer and respone. I hope it helps you.
My serialzier (extra field is JsonField)
class JobUserSerializer(serializers.ModelSerializer):
class Meta:
model = JobUser
fields = (
"email",
"is_active",
"is_staff",
"id",
"extra",
)
read_only_fields = (
"id",
)
My response (GET request to viewsets. You can see extra json data)
{
"email": "test5#test.com",
"is_active": true,
"is_staff": false,
"id": 13,
"extra": {
"last_name": "kim",
"first_name": "seul",
"gcf_spouses_name": "test",
"gcf_spouses_position": "test"
}
},
Update
I think you can use get_* method and use it.
this is example code for mine
class JobUserSerializer(serializers.ModelSerializer):
first_name_from_extra = serializers.SerializerMethodField()
class Meta:
model = JobUser
fields = (
"email",
"is_active",
"is_staff",
"id",
# "extra",
"first_name_from_extra"
)
read_only_fields = (
"id",
)
def get_first_name_from_extra(self, obj):
try:
return obj.extra['first_name']
except TypeError:
return ""
Then you can access directly "first_name" (my extra json field's key)
My response
{
"email": "test5#test.com",
"is_active": true,
"is_staff": false,
"id": 13,
"first_name_from_extra": "seul"
},
Be careful using this method: your json field SHOULD have that key. If one of your serializing model don't have the key, it raises NoneType TypeError. Or you can use try/except in get_* method.
Hope helping!
I'm a beginner of Json and Gson, I know I can map json into a class, and map a class to json via Gson.
"My Json" is a json data, I try to design a class "My Class" to map, but I think that "My Class" is not good. Could you show me some sample code? Thanks!
My Class
data class Setting (
val _id: Long,
val Bluetooth_Stauts: Boolean,
val WiFi_Name,String
val WiFi_Statuse: Boolean
)
My Json
{
"Setting": [
{
"id": "34345",
"Bluetooth": { "Status": "ON" },
"WiFi": { "Name": "MyConnect", "Status": "OFF" }
}
,
{
"id": "16454",
"Bluetooth": { "Status": "OFF" }
}
]
}
Updated
The following is made by Rivu Chakraborty's opinion, it can work well, but it's to complex, is there a simple way?
data class BluetoothDef(val Status:Boolean=false)
data class WiFiDef(val Name:String, val Status:Boolean=false)
data class MDetail (
val _id: Long,
val bluetooth: BluetoothDef,
val wiFi:WiFiDef
)
data class MDetailsList(val mListMetail: MutableList<MDetail>)
var mBluetoothDef1=BluetoothDef()
var mWiFiDef1=WiFiDef("MyConnect 1",true)
var aMDetail1= MDetail(5L,mBluetoothDef1,mWiFiDef1)
var mBluetoothDef2=BluetoothDef(true)
var mWiFiDef2=WiFiDef("MyConnect 2")
var aMDetail2= MDetail(6L,mBluetoothDef2,mWiFiDef2)
val mListMetail:MutableList<MDetail> = mutableListOf(aMDetail1,aMDetail2)
var aMDetailsList=MDetailsList(mListMetail)
val json = Gson().toJson(aMDetailsList)
As per your JSON Structure, I think below class definition should work with Gson
data class Setting (
val id: Long,
val Bluetooth: BluetoothDef,
val WiFi:WiFiDef
)
data class BluetoothDef(val Status:String)
data class WiFiDef(val Name:String, val Status:String)
Explanation -
If you're getting an object in your JSON, you should define a class for that to use with Gson.
Data types should match, use String if you're getting Strings like "ON" and "OFF". You can use Boolean if you're getting true and false (without quotes).
The JSON Element name should match the variable/property name unless you're using #SerializedName to define JSON variable name while using different variable/property name.
*Note You can rename the classes if you want
I think it'll be helpful for you
I'm trying to parse a random JSON file in Grails.
First I need to get the name of each field
For example, given below JSON file,
{
"abbreviation": "EX",
"guid": "1209812-1l2kj1j-fwefoj9283jf-ae",
"metadata": {
"dataOrigin": "Example"
},
"rooms":
[
],
"site": {
"guid": "1209812-1l2kj1j-fwefoj9283jf-ae"
},
"title": "Example!!"
}
I want to find out the structure of the JSON file(lists of keys maybe), for example I want to save the list of keys such as 'abbreviation', 'guid', 'metadata', 'rooms', 'site', 'title' from this JSON file.
How would I do this?
(We need the name of the keys in order to get the value of that key, so with a arbitrarily structured JSON file I need to find out the keys first)
You can try below code
def filePath = "JSONFILE.json"
def text = new File(filePath).getText()
def json = JSON.parse(text)
def jsonKeys = json.collect{it.key}
println(jsonKeys)
This will print all json keys
From what dmahaptro commented, I figured out how to get all the keys within a JSON object.
Here is a simple sample code I wrote to test it
String jsonFile = new JsonSlurper().parseText(new URL(path to the json file).text)
JSONArray jsonParse = new JSONArray(jsonFile)
int len = jsonParse.length()
def names = []
def keys = []
(0..len-1).each {
JSONObject val = jsonParse.getJSONObject(it)
int numKeys = val.length()
names = val.names()
keys = val.keySet()
(0..numKeys-1).each {
def field = names[it]
println field +" : " + val."${field}"
}
}
This will print the key:value pair given a JSON file.
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