49

Is there any way to deserialize JSON into an object using JSON.deserialize if some of the property names in the JSON are reserved words in apex? I want to do something like this:

string jsonString = '{"currency" : "ABC"}';
public class JSONResult
{
    public string currency;
}
JSONResult res = (JSONResult) JSON.deserialize(jsonString, JSONResult.class);
system.debug(res);

but of course I get an error that the identifier name is reserved.

  • In .Net you would prepend with '@'. Does Apex have something similar? – Mike Chale Oct 12 '12 at 18:54
  • Not that I was able to find but I'm really hoping that someone will be able to correct me. – Greg Grinberg Oct 12 '12 at 19:02
  • Is it an option to deserialize as Map<String,String>? – bigassforce Jan 15 '13 at 23:19
  • 2
    it wasn't in this case since I wanted to deserialize into fairly complex objects and going through the deserializeuntyped map would have been painful. I ended up using the string replace and it worked fairly well. – Greg Grinberg Jan 15 '13 at 23:35
45

There are 2 ways that you could solve this problem, neither of them is exactly what you're looking for, but I think it's the best apex offers.

  1. Perform a string replace on the json string, the implications of this is unintended replacement of valid text inside a string, but this could be mitigated if the form of the json is as you've supplied "currency": "ABC" you could string replace:

    jsonString.replace('"currency":', '"currency_x":');

  2. The other is a lot more painful, it would require you to parse the json yourself using the JSON Parser methods. This will be a lot more accurate, but if the definition of the json changes you will have to rewrite your solution

  • +1 for the string replace, it feels so hackish but is probably the best solution if OP doesn't control the JSON. – jkraybill Oct 13 '12 at 5:12
  • 5
    another half-baked feature from SF – NSjonas Aug 7 '15 at 2:36
  • SF deserializer achieves total parity with apex, and a tokenizing parser is provided for handling unserializable types; complete and exhaustive, no? @NSjonas – bigassforce Aug 14 '15 at 23:35
  • @bigassforce not really when compared to other "enterprise languages" (see .NET [DataMember]). Sure I could parse the whole string token by token, but I thought this was 2015. Combine this with the fact that you have no control how "null" values are written and the round trip serialization won't cut it for 95% of the JSON API's out there without serious string manipulation. – NSjonas Aug 15 '15 at 0:25
  • 1
    is fair @NSjonas, maybe possible to suffix reserved words combined with json2apex? – bigassforce Aug 22 '15 at 10:29
12

I've created a abstract class to allow for two-way serialization of JSON that has reserved keywords. There are definitely some limitations but it works for everything I've tried so far (google calendar & jira API).

/* Author: Charlie Jonas (charlie@callawaycloudconsulting.com)
 * Description: Allows reserved named serialization.
 * Usage:  See Readme @ https://github.com/ChuckJonas/APEX-JSONReservedNameSerializer
 */
public abstract class JSONReservedSerializer {
    private final Map<Type,Map<String,String>> typeMapKeys;

    public JSONReservedSerializer(Map<Type, Map<String, String>> typeMapKeys){
        this.typeMapKeys = typeMapKeys;
    }

    public String serialize(Object obj, System.Type type){
        return serialize(obj, false, type);
    }

    public String serialize(Object obj, Boolean suppressNulls, System.Type type){
        String retString = JSON.serialize(obj, suppressNulls);
        retString = transformStringForSerilization(retString, typeMapKeys.get(type));
        return retString;
    }

    public Object deserialize(String jsonString, System.Type type){
        jsonString = transformStringForDeserilization(jsonString, typeMapKeys.get(type));
        return JSON.deserialize(jsonString, type);
    }

    private static String transformStringForSerilization(String s, Map<String, String> mapKeys){
        return replaceAll(s, mapKeys);
    }

    private static String transformStringForDeserilization(String s, Map<String, String> mapKeys){
        Map<String,String> flippedMap = new Map<String,String>();
        for(String key : mapKeys.keySet()){
            flippedMap.put(mapKeys.get(key), key);
        }
        return replaceAll(s, flippedMap);
    }

    private static String replaceAll(String s, Map<String,String> toFromMap){
        for(String key : toFromMap.keySet()){
            s = s.replaceAll('"'+key+'"(\\ )*:', '"'+toFromMap.get(key)+'":');
        }
        return s;
    }
}

Implementation looks like this:

public class MySerializer extends JSONImprovedSerializer {

  private MySerializer() {
    //setup mappings
    super(new Map<Type,Map<String,String>>{
      MyOuterDTO.class => OUTER_DTO_MAPPINGS
    });
  }

  //define DTO's using mapped names
  static final Map<String, String> OUTER_DTO_MAPPINGS = new Map<String, String> {
      'obj' => 'object',
      'isPrivate' => 'private'
  };

  public class OuterDTO {
    public InnerDTO obj;
  }

  public class InnerDTO {
    public Boolean isPrivate;
    public String notReserved;
  }
}

Usage (round trip serialization):

String origString = '{"object":{"private":true,"notReserved":"abc"}}';

//deserialization
MySerializer json = new MySerializer();
MySerializer.OuterDTO dto = (MySerializer.OuterDTO) json.deserialize(
  origString,
  MySerializer.OuterDTO.class
);

//serialization
String newString = json.serialize(obj);
System.assertEquals(origString, newString);

UPDATE

I Created a to a repo with install instruction using the sfdx-cli

4

Can suffix the JSON programmatically and deserialize it into a generated class:

public class DTO {
    String toString_x;
    String object_x;
    String class_x;
    String new_x;
}

For example:

String data = '{"class": ""}'; //bad words etc...
Object input = Json.deserializeUntyped(data);

String suffixed = new ReservedWordSerializer(obj).getAsString();
DTO dto = (DTO)Json.deserialize(suffixed, DTO.class);

//no more bad words
System.debug(dto.class_x);

using the below generator to perform the suffixing:

/**
 * Usage:
 * new ReservedWordSerializer(obj).getAsString();
 */
public class ReservedWordSerializer {

    //true for pretty printing
    JsonGenerator g = Json.createGenerator(true);

    public ReservedWordSerializer(Object obj) {
        if (obj == null) {
            g.writeNull();
        } else if (obj instanceof Map<String,Object>) {
            traverseMap((Map<String,Object>)obj);
        } else if (obj instanceof List<Object>) {
            traverseList((List<Object>)obj);
        } else {
            g.writeObject(obj);
        }
    }

    public String getAsString() {
        return g.getAsString();
    }

    void traverseMap(Map<String,Object> obj) {
        List<String> keys = new List<String>(obj.keySet());
        keys.sort();

        g.writeStartObject();
        for (String key : keys) {
            Object value = obj.get(key);
            g.writeFieldName(key + '_x'); //<------- reserved word safety here

            if (value == null) {
                g.writeNull();
            } else if (value instanceof Map<String,Object>) {
                traverseMap((Map<String,Object>)value);
            } else if (value instanceof List<Object>) {
                traverseList((List<Object>)value);
            } else {
                g.writeObject(value);
            }
        }
        g.writeEndObject();
    }

    void traverseList(List<Object> objs) {
        g.writeStartArray();
        for (Object obj : objs) {
            if (obj == null) {
                g.writeNull();
            } else if (obj instanceof Map<String,Object>) {
                traverseMap((Map<String,Object>)obj);
            } else if (obj instanceof List<Object>) {
                traverseList((List<Object>)obj);
            } else {
                g.writeObject(obj);
            }
        }
        g.writeEndArray();
    }

}
/**
 * The MIT License (MIT)
 * 
 * Copyright (c) 2015 bigass.force.com
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
3

To add to the 2 ways in the accepted answer, here is a third way making use of Apex maps that allow any string as the key:

Map<String, Object> m = (Map<String, Object>)JSON.deserializeUntyped('{"QPA$MV2": "xyz"}');
String value = (String) m.get('QPA$MV2');
System.assertEquals('xyz', value);

This approach also works when you want to serialize to JSON.

1

We have to change the Key of JSON for sure .Reserved words cant be keys .We will run into compile time errors

  • I am not actually in control of the JSON. I am receiving it from an external API. – Greg Grinberg Oct 12 '12 at 19:02
  • 1
    Catch the JSON and write an algorithm to append x in the key .Assuming there are only few keys you may change – Mohith Shrivastava Oct 12 '12 at 19:26

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.