I have a web api endpoint that receives JSON and serializes it into objects. Very basic and common stuff. However, I have a requirement to accept custom user defined fields. For example, a developer may want to add a custom field for "account #" and pass that along via the API. I'm stuck how I could define a field on my class if I don't know the name. I need to support unlimited fields so I cannot simply create a field for custom1, custom2, custom2, etc.
I would think that my JSON could look something like this... where custom_label_xxx is identifies the field label:
...
"custom_fields": {
"custom_label_90": 49,
"custom_label_83": [ 28, 29, 30 ],
"custom_label_89": "2012/05/21"
},
...
How in the world can I setup a dynamic class to accept this dynamic JSON?
I have Googled forever and cannot find any examples using custom fields.
Your post method can accept dynamic as a param:
public HttpResponseMessage Post(dynamic obj)
This will accept every json that you send.
Saykor's reply is probably correct. However, I already have tons of business objects to work with and I only wanted the custom fields to be dynamic. Also, I wanted the Web API Help pages to be generated with sample JSON. So making the method dynamic would not work well for me.
What I finally did was probably obvious to some. I created a property on the class that was a dictionary. When I run the Help Pages it generates sample JSON that looks exactly what I needed. I have not tested this yet, but I assume it will serialize the JSON into a dictionary.
Here is the property in VB, but it could easily use dynamic in C# as well.
Private _customfields As Dictionary(Of String, String)
<DataMember(EmitDefaultValue:=False, IsRequired:=False, Order:=11)> _
Public Property customfields() As Dictionary(Of String, String)
Get
Return Me._customfields
End Get
Set(ByVal value As Dictionary(Of String, String))
Me._customfields = value
End Set
End Property
This resulted in the following JSON:
"customfields": {
"sample string 1": "sample string 2",
"sample string 3": "sample string 4",
"sample string 5": "sample string 6"
}
Related
In Visual Basic code, when I try to insert a variable into this JSON string it comes back
as a 400 bad request. How do I correctly feed this JSON string a variable?
Dim myJSON As String = "{""StoreId"":""12345"",""TerminalId"":""12345"",""CaptureMode"":""true"",""MerchantReferenceCode"":""VARIABLEINSERTEDHERE"",""InvoiceNumber"":""12345"",""TimeoutMinutes"":""5"",""ShowAddress"":""true"",""AddressRequired"":""true"",""TransactionTotal"":""33.33"",""TaxTotal"":""2.00"",""CustomerCode"":""12345""}"
Your JSON as it sits right now is an Object that looks like this:
{
"StoreId": "12345",
"TerminalId": "12345",
"CaptureMode": "true",
"MerchantReferenceCode": "VARIABLEINSERTEDHERE",
"InvoiceNumber": "12345",
"TimeoutMinutes": "5",
"ShowAddress": "true",
"AddressRequired": "true",
"TransactionTotal": "33.33",
"TaxTotal": "2.00",
"CustomerCode": "12345"
}
One option that you have is to create a new JObject, use the Add method (documentation) to build the object's properties, and then the ToString method (documentation) to serialize the object to JSON. This way you don't have to worry about properly formatting the JSON, just let the library do it for you.
Take a look at this example:
Dim myVariable = "VARIABLEINSTEREDHERE"
Dim request = New JObject()
request.Add("StoreId", 12345)
request.Add("TerminalId", 12345)
request.Add("CaptureMode", True)
request.Add("MerchantReferenceCode", myVariable)
request.Add("InvoiceNumber", 12345)
request.Add("TimeoutMinutes", 5)
request.Add("ShowAddress", True)
request.Add("AddressRequired", True)
request.Add("TransactionTotal", 33.33)
request.Add("TaxTotal", 2.00) ' taxation is theft
request.Add("CustomerCode", 12345)
Dim myJson = request.ToString()
Example: https://dotnetfiddle.net/FQDpVq
The short answer to the question you asked:
myjson = myjson.replace("VALUEPLACEHOLDER1","SoMeTHiNGeLSe")
Now, if you are talking about tricky pattern matching, then you will need to use regex if you want to approach this from a string-manipulation perspective. That sets you up for all sorts of unanticipated problems down the road with things you didn't anticipate, though, and you'll probably be happier in the long run if you embrace using JSON.
Try using the NewtonSoft JSON libraries. Then you can do something like:
dim myobj as new MyClass
myobj = jsonconvert.deserializeobject(of MyClass)(myjson)
That will turn your JSON string into an instance of MyClass called myobj, and you can access its properties directly, changing whatever you want:
myobj.MerchantReferenceCode = "SpecialMerchant"
Note that if the class definition for MyClass has properties that the JSON has no values for, they'll be present in the new class instance with no values. You can then set those values as you wish.
If the JSON has properties that don't exist in your class, they'll be dropped/lost in the conversion, so you do need to study your source data.
And you turn it back into a string again easily:
dim newjson as string = jsonconvert.serializeobject(myobj)
I have a Data class which I populate manually from a XML returned from a trading Partner. Most of time the returned Data will only have a few of the fields in my class populated, the others I set to Nothing(null) and then use the
Dim settings As New JsonSerializerSettings
settings.NullValueHandling = NullValueHandling.Ignore
to suppress any field with no data. All that works great but I am wondering how I can do the same or similar for List of Objects in my class. Right now if there is no data for one of these I still get an an output like this which is an empty json Array.
{
"Txnum": "APO100000007R",
"Dtsent": "20180625105938",
"Txtyp": "A",
"Location": [],
"Terminationdata": [],
"Responsestatus": {
"prespc": "FTRAVQ059",
"prespd": "LV1 IS REQUIRED "
}
}
So I am wondering if and how I could avoid this short of checking json string before I return it and strip the "Location": [], for example.
This is the code that worked for me. Just as a Side Node the ShouldSerialize"PropertyName:() is case sensitive so ShouldSerializeLocation() works while ShouldSerializelocation() does not. In Vb code normally is not case sensitive so just watch for that to save your self some headache.
<XmlElement([Namespace]:=SynchronossNS)>
Public Property Location() As List(Of location)
Get
Return _location
End Get
Set
_location = Value
End Set
End Property
Public Function ShouldSerializeLocation() As Boolean
Return _location.Count > 0
End Function
I am using Swagger to document a REST API.
I am having a class like this:
public class Source{
private String url;
private String category;
private String label;
...
}
I am currently using #ApiImplicitParam to set the dataType to Source.class, but I am having multiple POST requests that get a JSON as a body parameter with, lets say, a single variable of those, for example:
{"label": "labelA"}
Because of the dataType set previously, the example value displayed by the Swagger UI is a whole Source.class, something like this:
{
"url": "string",
"category": "string",
"label": "string",
...
}
Could I somehow chop the example value displayed by the Swagger UI for every single request of those? I mean that the getSourceFromUrl() request should get a JSON that contains only a url field, and the example should display exactly this and not the full Source.class JSON.
Thank you all in advance!
UPDATE
I am using JAX-RS. Please, ask me for more input if needed.
If you are using springfox-swagger2 , there is an annotation #ApiModelProperty that does this.
Example:
#ApiModelProperty(required = false, hidden = true)
private String label;
For the time being, you cannot do such a thing. You have to create a different class for each case.
See in swagger-core's github: https://github.com/swagger-api/swagger-core/issues/1863#issuecomment-237339565
tl;dr
I need to send the data from a LinkedHashMap in a GSP template to a Controller and preserve the order of the elements.
I'm assuming a structured data format like JSON is the ideal way to do this, but Grails' JSON converter doesn't create an ordered JSON object from a LinkedHashMap.
What is the best way to send a LinkedHashMap data structure from a GSP to a Controller so that I can preserve order, but do minimal work in parsing the data?
Long version
I'm developing a taglib to render search results in a table.
In the taglib, I construct a LinkedHashMap that specifies the data columns and the labels that the user wants to show for the column names. For example:
def tableFields = [firstName: "First Name", lastName: "Surname", unique_id: "Your Whizbang ID"]
That map gets sent to a view, which will then send it back to a controller to retrieve the search results from the database. I need to preserve the order of the elements (hence the use of a LinkedHashMap).
My first thought was to turn the LinkedHashMap into a JSON string, and then send it to the controller via a hidden form element. So,
import grails.converters.JSON
//taglib class and other code
def tableFields = [firstName: "First Name", lastName: "Surname", unique_id: "Your Whizbang ID"] as JSON
However, that creates a JSON Object like this in the HTML. I'm putting this in a hidden field's value attribute.
<input type="hidden" name="columns" value="{"firstName": "First Name", "lastName": "Surname", "unique_id": "Your Whizbang ID"}" id="columns">
Here's the JSON object by itself.
{"firstName": "First Name", "lastName": "Surname", "unique_id": "Your Whizbang ID"}
You can see that the JSON string's properties are in the same order as the LinkedHashMap in the JSON string. However, JSON Objects aren't really supposed to the preserve order of their properties. Thus, when my controller receives the columns parameter, and I use the JSON.parse() method on it, it creates a plain ol' unordered HashMap instead of a LinkedHashMap. As a result, the columns in my search results display in the wrong order when I render them into an HTML table.
At least one fellow has had a similar problem. Adding as LinkedHashMap after running JSON.parse() doesn't cut it, since the .parse() method screws up the order from the get go.
Daniel Woods, in his response to the above post, noted:
If it's a matter of the grails data binder not working for you, you should be able to override the implicit property setter to cast the object to your favorite Map implementation.
I assume that he's saying I could write my own parser, which would honor the order of the JSON elements (even though it technically shouldn't). I imagine I could also write my own converter so that the resulting JSON element would be something like:
{[{firstName: "First Name"}, {lastName: "Surname"}, {unique_id "Your Whizbang ID"}]}
I'm just about terrified of how the JSON parser would handle that, though. Would I get back a list of HashMaps?
Again, my real question is What is the best way to send a LinkedHashMap data structure from a GSP to a Controller so that I can preserve order, but do minimal work in parsing the data? I'm assuming that's JSON, but I'm more than happy to be told, "Why not just..."
I think the issue is a mismatch between the nature of Java/Groovy collections and the simple "it's a list or it's a map" nature of JSON. Without getting into custom parsing, I'd suggest shifting what you're sending a bit. Instead of trying to force Groovy notions of a LinkedHashMap into Javascriptland, maybe stick to an idiom Javascript understands, such as a list of maps.
In code, instead of:
def tableFields = [firstName: "First Name", lastName: "Surname", unique_id: "Your Whizbang ID"]
how about:
List tableFields = [
[ name: 'firstName', label: 'First Name' ],
[ name: 'lastName', label: 'Surname' ],
[ name: 'unique_id', label: 'Your Whizbang ID' ],
]
This shifts you to JSON that'd maintain the data (I think) you need while giving JSON something it understands is ordered (a list):
<input type="hidden" name="columns" id="columns" value="[
{ "name": "firstName", "label": "First Name" },
{ "name": "lastName", "label": "Surname" },
{ "name": "unique_id", "label": "Your Whizbang ID" }
]" />
Whatever handles this will be a slightly deeper iterator, but that's the price of going from a land of good collections to simpler types...
What I'm doing for now is passing both the current JSON object and a list that I can iterate through. In the GSP template, this looks like:
<g:hiddenField name="columns" value="${colJson}"/>
<g:hiddenField name="columnOrder" value="${columns.collect{it.key}}"/>
where columns is the LinkedHashMap.
Then, in the controller that gets those params, I do this:
def columnTitles = params.columnOrder.tokenize(",[] ")
def unorderedColumns = JSON.parse(params.columns)
def columns = columnTitles.collectEntries{ [(it): unorderedColumns[it]] }
Not elegant, but it does work, and it requires a bit less refactoring than Joe Rinehart's suggestion.
Consider that you get this JSON object:
{ id: 3, name: 'C' }
How can you tell if it's a Vitamin object or a Programming language object. Am I clear?
In typed languages, we simply understand the nature of the object (the Type of the object) from its very name. Each object has a name. How we might achieve something similar with JSON? How can we give names to JSON objects? Any established pattern?
If you have this problem it means that your serialization format is not properly defined. You should always be able to deserialize from a JSON object without any trouble.
There are two options:
Add a type field: You can't create the typed object because there are multiple types with the same set of field names. By adding a type field, there is no question about the underlying type of the object. With this option, you will have to go through the process of handling the type field when creating the real object.
Use context to decide what type the object is: If the query you submitted implies that only one type of object should be returned, then there is no problem. Alternately, if multiple types can be returned, the response format should group the objects by type.
{
"states": [ { id: 3, name: 'New York' } ],
"cities": [ { id: 4, name: 'New York' } ]
}
You have to add a field describing the type of the object. That way you can never have doubts about the type. Look for example at Google's Calendar API. Google's calendar resources all have a field "kind" describing the type.
A Calendar Event looks like:
{
"kind": "calendar#event",
"etag": etag,
"id": string,
"created": datetime,
"updated": datetime,
"summary": string,
...
}
A Calendar List Entry like:
{
"kind": "calendar#calendarListEntry",
"etag": etag,
"id": string,
"summary": string,
"description": string,
"location": string,
...
}
etc.
There's no way to do it for JSON fetched from somewhere else.
If you have control over the JSON, then you can do this:
Add a "type" field to each object.
Tailor make a JSON function to handle this. This can be done in two ways, one secure, one insecure.
Secure method
Create a function stringifyJSONType(). This one stringifies as usual, but adds a type parameter on-the-fly.
function stringifyJSONType(o){
o.type=o.constructor.name;
var s=JSON.stringify(o);
delete o.type; //To be clean and not modify the object.
return s;
}
Now, in the "secure" method, we have to create a switch-case for every type we expect for parsing. This only allows certain types (those which have been kept in the switch-case).
function parseJSONType(s){
var o=JSON.parse(s);
switch(o.type){
case "String":
o.__proto__=String;
break;
case "Date":
o.__proto__=Date;
break;
case "City": //Your custom object
o.__proto__=City;
break;
case "State": //Your custom object
o.__proto__=State;
break;
case "Country": //Your custom object
o.__proto__=Country;
break;
/*.... more stuff... */
case "Object":
default:
o.__proto__=Object;
break;
}
delete o.type;
return o;
}
Now, use these two methods just like JSON.parse() and JSON.stringify(), and it'll work. But for every new type you want to support, you'll have to add an extra case.
Insecure method
Not too insecure, just that it uses the nefarious eval() method. Which isn't too good.. As long as nobody else has the ability to add a custom type parameter to your JSON, it's OK, though.
Here, you use the same stringifyJSONType() as above, but use a different parse method.
function stringifyJSONType(o){
o.type=o.constructor.name;
var s=JSON.stringify(o);
delete o.type; //To be clean and not modify the object.
return s;
}
function parseJSONType(s){
var o=JSON.parse(s);
o.__proto__=eval(o.type);
delete o.type;
return o;
}
This has the advantage of not requiring switch-case and being easily extended to new types (no code changes required).