Skip to content

Key translation mutates JSON attributes on serialized models #1834

Closed
rails-api/case_transform
#8
@bdmac

Description

@bdmac
Contributor

Expected behavior vs actual behavior

c533d1a added support for key transformation to e.g. snake case or dasherized. Nice and useful however it appears to use deep_transform_keys!. I'm not 100% sure but I have a feeling this is causing serialized models with JSON attributes (e.g. postgres JSON columns) to be mutated when they should not be.

In short serialization of a model object with JSON columns leaves that model object with dirty with unsaved (and obviously unexpected) changes.

Expected behavior is to NOT mutate model attributes during serialization.

Steps to reproduce

Setup a model with a JSON column. Serialize an instance of that object. Notice that the JSON attribute is now dirty/has changes on the model.

> model = Model.first
# Assume model.schema is a JSON attribute that is included in the serializer for Model
> ActiveModel::SerializableResource.new(model).to_json
> model.schema_changed?
true

Environment

ActiveModelSerializers Version (commit ref if not on tag): 0.10.2

Output of ruby -e "puts RUBY_DESCRIPTION": ruby 2.3.0p0 (2015-12-25 revision 53290) [x86_64-darwin14]

OS Type & Version: OSX El Capitan

Integrated application and version (e.g., Rails, Grape, etc): Rails

Additonal helpful information

This bug is not present in versions of AMS prior to this commit: c533d1a

Activity

bdmac

bdmac commented on Jul 8, 2016

@bdmac
ContributorAuthor

A second part of this bug is that I do not think it would be expected behavior that if a model includes a JSON attribute, that that JSON attribute would ALSO go through the same transformations as the model/response itself. I mean maybe but it seems like that should be something that can be opted in/out at least.

bdmac

bdmac commented on Jul 8, 2016

@bdmac
ContributorAuthor

I can fix the mutation problem by simply not using the bang version of deep_transform_keys! in ActiveModelSerializers::KeyTransform.

I'm not sure how to make it so a JSON attribute of a serialized object is not transformed...

bdmac

bdmac commented on Jul 8, 2016

@bdmac
ContributorAuthor

Eek yah I tried to look at KeyTransform a bit and it seems like it would be pretty damn hard to make it not transform JSON model attributes since it just gets the entire Hash structure and recursively transforms all the keys at once. It does not have access to the underlying/original models to even try to check if the nested JSON it's dealing with is there because of a JSON attribute/column or not...

bf4

bf4 commented on Jul 8, 2016

@bf4
Member

@bdmac we shouldn't be mutating the model, no matter what. Is that specific to using a JSON field? I'd be happy to work with you on PR for that.

bdmac

bdmac commented on Jul 8, 2016

@bdmac
ContributorAuthor

@bf4 I believe so, yes, because we use the mutating version of deep_transform_keys and it seems like the hash representing the JSON column is sharing a pointer with what was pulled from the model originally still. E.g. at some point when we are building up the hash representation of the model with a JSON attribute we are just doing hash[attribute_name] = model.read_attribute(attribute_name).

Two possibilities I can think of to solve the initial problem:

  1. Don't use the ! version in KeyTransform. This would have some memory overhead.
  2. Figure out where exactly we are pulling the JSON values out of the model for serialization and dup it there. Continue using ! version in KeyTransform as now we would be safely dissociated from the model attribute.
bf4

bf4 commented on Jul 8, 2016

@bf4
Member

@bdmac fwiw, the key transform is a performance hit you might resolve better on the client...

bdmac

bdmac commented on Jul 8, 2016

@bdmac
ContributorAuthor

@bf4 that may be true but transforming is the default behavior of the JsonApi adapter in AMS.

bf4

bf4 commented on Jul 8, 2016

@bf4
Member

@bdmac yeah, in retrospect I don't think it was a good decision. I have it set to :unaltered in my own app. Picking good defaults is hard

bdmac

bdmac commented on Jul 8, 2016

@bdmac
ContributorAuthor

I mean maybe it was a bad decision but the JSON API spec calls for dasherized keys so even if you did not automatically dasherize, the end-user would end up needing to dasherize anyways. We were dasherizing prior to attempting to update to 0.10.2 (which includes the KeyTransform stuff) just in a way that did not mutate (or recursively dasherize everything).

bdmac

bdmac commented on Jul 8, 2016

@bdmac
ContributorAuthor

@bf4 FWIW I've been able to work around this problem. I turned off key transformation as you mentioned by setting key_transform config to :unaltered. Here is our initializer now for reference:

class DasherizedJsonApiAdapter < ActiveModel::Serializer::Adapter::JsonApi
  private

  def resource_identifier_for(serializer)
    hash = super
    hash[:type] = hash[:type].to_s.underscore.dasherize
    hash
  end

  def resource_object_for(serializer)
    hash = super
    if hash[:attributes].present?
      hash[:attributes].transform_keys!{|key| key.to_s.underscore.dasherize}
    end
    hash
  end

  def relationships_for(serializer)
    super.transform_keys!{|key| key.to_s.underscore.dasherize}
  end
end

ActiveModel::Serializer.config.adapter = DasherizedJsonApiAdapter
ActiveModel::Serializer.config.key_transform = :unaltered

We were already/previously using the custom DasherizedJsonApiAdapter adapter since pre-release versions of 0.10.x did not have support for key transform / dasherized keys. I was hoping to get rid of our custom adapter by just setting the adapter to :json_api when we upgraded to 0.10.2 but that brought up all these issues...

29 remaining items

Loading
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

      Development

      Participants

      @jfine@remear@bdmac@bf4@NullVoxPopuli

      Issue actions

        Key translation mutates JSON attributes on serialized models · Issue #1834 · rails-api/active_model_serializers