Any way to modify the contents of a JSON::Any type?

Since the JSON::Any type is immutable, the only way I can think of is using JSON.build to create new a JSON using the same contents of the JSON::Any variable but with the ability of modifying it’s values.

For example:

require "json"

string = %([{"value": "a value", "persistentValue": "DO NOT CHANGE ME!"}])
json = JSON.parse(string)
puts typeof(json)
pp json

# This actually adds a new key and that is not what I want to do.
# json.as_a[0].as_h["bar"] = JSON::Any.new("foo")

mJson = JSON.build do |j|
  j.object do
    j.field "value", "other value"
    j.field "persistentValue", json[0]["persistentValue"]
  end
end

puts typeof(mJson)
pp mJson

This of course works well but there is another (or a better and proper) way to do it?

If you know the structure of the JSON, it’ll be much easier to work with if you use actual types. E.g. in this case Array(Hash(String, String)).from_json string, then you’ll have an actual array of hashes and not JSON::Any. For more complex data you could look into JSON::Serializable - Crystal 1.12.1.

There seems to be a distinction here that I’m missing. The title of the thread indicates that you want to modify the contents, but you don’t want setting a key that doesn’t exist in the hash to set a new key.

Can you clarify how you do want to modify it?

The JSON::Any wrapper itself is immutable, but its contents are not. Unless they’re immutable types themselves, but the container types (Array and Hash) are certainly mutable. So you can perfectly fine mutate a structure consisting of JSON::Any.

The title of the thread indicates that you want to modify the contents

Yes, that I want to do. I added that commented line because of a related thread that adds a field to a JSON::Any type variable: Adding a field to a json object - #2 by asterite

Can you clarify how you do want to modify it?

By changing any value inside the JSON object. I do not need to add a field.

I’m not really good at explaining things (especially with languages I am currently learning, in this case, Crystal) so ask me anything in case what I said was not so clear.

You can set any key you like, so you could set one of the existing keys:

json.as_a[0].as_h["value"] = JSON::Any.new("new value")

Or you can instead define a class type to enforce the idea that you can’t add new keys, but you can mutate specific ones:

require "json"

class Thing
  include JSON::Serializable

  # You can set this one because it is a `property`
  property value : String

  # Set the camelCase name into a snake_case instance variable
  @[JSON::Field(key: "persistentValue")]
  # This one is a `getter` so you can't mutate it
  getter persistent_value : String
end

Then you can parse the string:

string = %([{"value": "a value", "persistentValue": "DO NOT CHANGE ME!"}])
things = Array(Thing).from_json(string)

puts things
# [#<Thing:0x100ea0e40 @value="a value", @persistent_value="DO NOT CHANGE ME!">]

… and then mutate the object inside the array:

things[0].value = "new value"

puts things.to_json
# [{"value":"new value","persistentValue":"DO NOT CHANGE ME!"}]

Defining a JSON::Serializable type is the most graceful way to do it. Compare the two mutations:

json.as_a[0].as_h["value"] = JSON::Any.new("new value")

# vs

things[0].value = "new value"
1 Like