How do you deserialize json properties that are reserved words in apex?

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


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 ([email protected])
 * 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


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.
 */

Tags:

Json

Apex