I want to set an arbitrary attribute for rendering to JSON.
I had followed the answer in this question: how to append data to json in ruby/rails? to do
model = Model.find
model[:extra_info] = "More detail."
model.to_json
It works perfectly, but in my tests I'm getting a deprecation warning that setting arbitrary attributes is no longer supported, use attr_writer.
I tried using
model.write_attribute(:extra_info, "More detail.")
which works in unit testing, but on the server, raises an exception:
private method `write_attribute' called for Model
What's the non-deprecated clean way to do this.
I'm aware I could set it in the JSON call with methods as in Add virtual attribute to json output, but in this case the variable to be added is not part of the models concern, so it doesn't have access to the data needed to construct the extra attribute, and it would be nasty and messy to do so.
So what's the correct way for the controller to get this data pushed into the model so the JSON renders properly?
In Model model, put
attr_accessor :extra_info
Then in controller
model.extra_info = "more detail"
Nick's answer above is the best in terms of creating well structured, well documented code.
Update
*It seems I was wrong on the below*
The code below still creates deprecation warnings on my development server.
In this particular case, I don't want to clutter the model up with extra accessors for very specific once off cases, so I'm using
s.send(:write_attribute, :extra_info, "more detail")
Inside a helper, inside the controller.
Related
I'm making a website that gets its info from a RESTapi I've written and hosted myself, have had no data problems thus far.
Now I'm trying a simple retrieve of a json object and I get all the info correctly as shown here in the API. (Visualized & tested in Swagger)
As you can clearly see, it retrieves it the complete object and underlying objects correctly (blurred sensitive info).
Pay attention to the property "AmountOfEggs" though.
Now when i call this api endpoint (exactly the same way) in my site and console.log the result.data, this is the feedback.
Now for some reason I can't recieve AmountOfEggs in my frontend.
I've recreated the entire object, wrote different names, passed different props (Id, NumberBus, etc are passed in this situation with no problem, which are also int (number) types).
I have no idea why this property gets "dropped" in the transfer and filled with/defaults to an empty string.
Any help would be greatly appreciated.
I found where it went wrong and it's due to different factors.
To start, I am making this site using the Vue framework, which has reactive components, which means, data gets adjusted and you get live updated components on your views/pages.
In the page that contained the call, I also wanted to add dynamic styling. The code I used for this is the following:
v-for="seller in retrievedSellers"
:key="seller.Id"
:class="[
'sellerStyle'
, (seller.AmountOfEggs = 0 ? 'grey-out-seller' : ''),
]"
Now two things to note, first of all this might look like backend coding but this is Vue logic on the .vue file which handles the dynamic options that Vue provides so I never thought to look here for the error.
The error of couse is the 'seller.AmountOfEggs = 0' part, using one equal sign (assignment) instead of two (comparison).
So I learned,
Vue doesn't throw errors on assignments in code that is used to generate frontend (where you should NEVER alter your data).
My console.log was the moment the response of the API came in, where apparently it was already assigned a new value (due to my code above) before hitting the console.log.
It still greatly surprises me that Vue handles ^this^ first to make sure your reactive components are up to date before finishing the api call itself.
However, I still don't understand how it said "" instead of 0.
I'm using typescript (javascript strongly-typed) so it definitely wants to contain a number and not an empty string. Even in the declaration of my DTO the property is defined as (and expects) a number.
I have a Django backend with conventional snake_case model fields. In the frontend, however, they expect another notation. Is there a simple way of configuring a serializer so that every field (e.g. field_name) gets automatically converted to the frontend notation (e.g. fieldName or even something completely different)? I couldn't find any simple way of doing this. I know I can manually declare each field and use source, but besides being a lot of extra work, it still only works for readonly fields if I remember correctly. Besides, it also forces me to declare fields like fieldName = serializers.CharField(source="field_name", ...), and it makes it impossible to use keys like field-name in the frontend. Is there any better way of doing it?
You can create a render or a parser in DRF to achieve this behavior. There is a package you can use to do that without changing your serializer. pip install djangorestframework-camel-case
I'm trying to create a very simple case: a controller action which renders a static JSON, from a template.
controller:
defmodule MyApp.TestController do
use Phoenix.Controller
def show(conn, _params) do
render(conn, "show.json")
end
end
view:
defmodule MyApp.TestView do
use MyApp.Web, :view
end
show.json.eex:
{
"message": "Hello, world!"
}
The problem is, I get the proper JSON response, but JSON-encoded:
"{\n \"message\": \"Hello, world!\"\n}"
Any idea why, and how to solve it?
/Edit:
I found out that I can work around the problem by renaming the template to something other than json (plus explicitly setting response type, of course), so obviously JSON templates are additionally encoded. But why, who would want such a thing?
After further investigation and talking with people on Phoenix Slack channel, I have a clearer picture about what's going on:
Phoenix is agnostic when it comes to deciding whether the content comes from a template or from a data structure in the view. render/2 from the controller happily takes anything and converts it to JSON.
I wrote up a more detailed blog post about this issue, along with several approaches in addressing it, and in my opinion every is a workaround for the inherent problem in Phoenix (which, admittedly, is far from critical).
Essentially, one should avoid executing Poison.encode function, which gets called from render_to_iostream function. You can do that either by not using .json as the template extension, by directly calling Phoenix.View.render from the controller, or by creating custom encoder and template engine to pass some metadata along with the data to be output.
Here's my JSON response for http://localhost:8000/characters/api/users/1?format=json
)]}',
{"id":1,"username":"admin","mage_by_user":[3],"mage_last_updated":"2015-02-11T16:13:16.229Z"}
Notice the )]}', on the first line.
Here is my code that gets called to create the JSON:
class UserSerializer(serializers.ModelSerializer):
mage_by_user = serializers.PrimaryKeyRelatedField(
many=True, queryset=Mage.objects.all())
mage_last_updated = serializers.ReadOnlyField(
source='mage_by_user.updated_date')
class Meta:
model = User
fields = ('id', 'username', 'mage_by_user', 'mage_last_updated',)
Further testing:
I've noticed the title of the page is TypeError at <insert url here>.
This happens with all of my endpoints
If I try to access a non-existent object (userId=2 for instance), then renders 'normally' for DRF, e.g:
{
detail: "Not found"
}
Any idea why this would happen?
Those characters are inserted by the Djangular middleware AngularJsonVulnerabilityMiddleware, to inject Json Vulnerability Protection
A JSON vulnerability allows third party website to turn your JSON resource URL into JSONP request under some conditions. To counter this your server can prefix all JSON requests with following string ")]}',\n". Angular will automatically strip the prefix before processing it as JSON.
Unfortunately, it means it breaks various JSON viewers.
Sorry to not be more help, but this looks like something entirely unrelated to REST framework. There's absolutely no way a JSON response there would ever be rendered in that way.
Perhaps you have a custom renderer configured, that's outputting a malformed response, perhaps you have some broken middleware inserting those characters, perhaps its an issue in the client or whatever environment you're making the requests, or perhaps it's something else entirely unrelated to any of those.
I'd start by trying to narrow down the issue as much as possible - remove all the complexity from the view and serializer and attempt to replicate the behavior in a test case.
Most likely there's some sort of unexpected integration issue you're missing or some otherwise obvious code typo that's being overlooked.
I'm developing a RESTful interface which is used to provide JSON data for a JavaScript application.
On the server side I use Grails 1.3.7 and use GORM Domain Objects for persistence. I implemented a custom JSON Marshaller to support marshalling the nested domain objects
Here are sample domain objects:
class SampleDomain {
static mapping = { nest2 cascade: 'all' }
String someString
SampleDomainNested nest2
}
and
class SampleDomainNested {
String someField
}
The SampleDomain resource is published under the URL /rs/sample/ so /rs/sample/1 points to the SampleDomain object with ID 1
When I render the resource using my custom json marshaller (GET on /rs/sample/1), I get the following data:
{
"someString" : "somevalue1",
"nest2" : {
"someField" : "someothervalue"
}
}
which is exactly what I want.
Now comes the problem: I try to send the same data to the resource /rs/sample/1 via PUT.
To bind the json data to the Domain Object, the controller handling the request calls def domain = SampleDomain.get(id) and domain.properties = data where data is the unmarshalled object.
The binding for the "someString" field is working just fine, but the nested object is not populated using the nested data so I get an error that the property "nest2" is null, which is not allowed.
I already tried implementing a custom PropertyEditorSupport as well as a StructuredPropertyEditor and register the editor for the class.
Strangely, the editor only gets called when I supply non-nested values. So when I send the following to the server via PUT (which doesn't make any sense ;) )
{
"someString" : "somevalue1",
"nest2" : "test"
}
at least the property editor gets called.
I looked at the code of the GrailsDataBinder. I found out that setting properties of an association seems to work by specifying the path of the association instead of providing a map, so the following works as well:
{
"someString" : "somevalue1",
"nest2.somefield" : "someothervalue"
}
but this doesn't help me since I don't want to implement a custom JavaScript to JSON object serializer.
Is it possible to use Grails data binding using nested maps? Or do I really heave to implement that by hand for each domain class?
Thanks a lot,
Martin
Since this question got upvoted several times I would like to share what I did in the end:
Since I had some more requirements to be implemented like security etc. I implemented a service layer which hides the domain objects from the controllers. I introduced a "dynamic DTO layer" which translates Domain Objects to Groovy Maps which can be serialized easily using the standard serializers and which implements the updates manually. All the semi-automatic/meta-programming/command pattern/... based solutions I tried to implement failed at some point, mostly resulting in strange GORM errors or a lot of configuration code (and a lot of frustration). The update and serialization methods for the DTOs are fairly straightforward and could be implemented very quickly. It does not introduce a lot of duplicate code as well since you have to specify how your domain objects are serialized anyway if you don't want to publish your internal domain object structure. Maybe it's not the most elegant solution but it was the only solution which really worked for me. It also allows me to implement batch updates since the update logic is not connected to the http requests any more.
However I must say that I don't think that grails is the appropriate tech stack best suited for this kind of application, since it makes your application very heavy-weight and inflexbile. My experience is that once you start doing things which are not supported by the framework by default, it starts getting messy. Furthermore, I don't like the fact that the "repository" layer in grails essentially only exists as a part of the domain objects which introduced a lot of problems and resulted in several "proxy services" emulating a repository layer. If you start building an application using a json rest interface, I would suggest to either go for a very light-weight technology like node.js or, if you want to/have to stick to a java based stack, use standard spring framework + spring mvc + spring data with a nice and clean dto layer (this is what I've migrated to and it works like a charm). You don't have to write a lot of boilerplate code and you are completely in control of what's actually happening. Furthermore you get strong typing which increases developer productivity as well as maintainability and which legitimates the additional LOCs. And of course strong typing means strong tooling!
I started writing a blog entry describing the architecture I came up with (with a sample project of course), however I don't have a lot of time right now to finish it. When it's done I'm going to link to it here for reference.
Hope this can serve as inspiration for people experiencing similar problems.
Cheers!
It requires you to provide teh class name:
{ class:"SampleDomain", someString: "abc",
nest2: { class: "SampleDomainNested", someField:"def" }
}
I know, it requires different input that the output it produces.
As I mentioned in the comment earlier, you might be better off using the gson library.
Not sure why you wrote your own json marshaller, with xstream around.
See http://x-stream.github.io/json-tutorial.html
We have been very happy with xstream for our back end (grails based) services and this way you can render marshall in xml or json, or override the default marshalling for a specific object if you like.
Jettison seems to produce a more compact less human readable JSON and you can run into some library collision stuff, but the default internal json stream renderer is decent.
If you are going to publish the service to the public, you will want to take the time to return appropriate HTTP protocol responses for errors etc... ($.02)