Mask value of selected key in JSON - java

I have JSON request and response, I want to print the JSONs in the log, but there are some secured fields which I want to avoid to print in the log, I am trying to mask fields keys:
example:
before masking:
{"username":"user1","password":"123456","country":"US","creditCardNumber":"1283-1238-0458-3458"}
after masking
{"username":"user1","password":"XXXXXX","country":"US","creditCardNumber":"XXXXXX"}
I am using java Gson lib, please help me to do that
EDIT
I want to pass the keys dynamically, so in function a I want to mask these fields, but in function b different fields.

I think you should exclude that fields from log. Below is a simple example using Gson and #Expose annotation.
public static void main(String[] args) throws IOException {
String json = "{\"username\":\"user1\",\"password\":\"123456\",\"country\":\"US\",\"creditCardNumber\":\"1283-1238-0458-3458\"}";
Gson gson = new Gson();
User user = gson.fromJson(json, User.class);
System.out.println(gson.toJson(user));
Gson gsonExpose = new GsonBuilder().excludeFieldsWithoutExposeAnnotation().create();
System.out.println(gsonExpose.toJson(user));
}
public class User {
#Expose
private String username;
private String password;
#Expose
private String country;
private String creditCardNumber;
}
Output will be:
{"username":"user1","password":"123456","country":"US","creditCardNumber":"1283-1238-0458-3458"}
{"username":"user1","country":"US"}
Another solution using Reflection:
public static void main(String[] args) throws IOException {
String json = "{\"username\":\"user1\",\"password\":\"123456\",\"country\":\"US\",\"creditCardNumber\":\"1283-1238-0458-3458\"}";
Gson gson = new Gson();
User user = gson.fromJson(json, User.class);
List<String> fieldNames = Arrays.asList("password", "creditCardNumber");
System.out.println(mask(user, fieldNames, "XXXXXXX"));
}
public static String mask(Object object, List<String> fieldNames, String mask) {
Field[] fields = object.getClass().getDeclaredFields();
for (int i = 0; i < fields.length; i++) {
if (fieldNames.contains(fields[i].getName())) {
try {
fields[i].setAccessible(true);
if (fields[i].get(object) != null) {
fields[i].set(object, mask);
}
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
Gson gson = new Gson();
return gson.toJson(object);
}

I like the above solution to mask using reflection but wanted to extend same for other field types and saving masked field to unmask again.
Create annotation #MaskedField on top of field.
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.FIELD)
public #interface MaskedField {
}
public <T> Map<String,? super Object> maskObjectFields(T object){
Map<String,? super Object> values = new HashMap<>();
Arrays.stream(object.getClass().getDeclaredFields()).filter(field->null != field.getAnnotation(MaskedField.class)).
forEach(annotatedField->{
try {
if(annotatedField.getType().isAssignableFrom(String.class)) {
annotatedField.setAccessible(true);
values.put(annotatedField.getName(),annotatedField.get(object));
annotatedField.set(object, maskString((String) annotatedField.get(object)));
}
} catch (IllegalAccessException e) {
e.printStackTrace();
}
});
return values;
}
public <T> void unMaskObjectFields(T object,Map values){
Arrays.stream(object.getClass().getDeclaredFields()).filter(field->null != field.getAnnotation(MaskedField.class)).
forEach(annotatedField->{
try {
annotatedField.setAccessible(true);
annotatedField.set(object,values.get(annotatedField.getName()));
} catch (IllegalAccessException e) {
e.printStackTrace();
}
});
}
private String maskString(String value){
if(Objects.isNull(value)) return null;
return null; //TODO: your logic goes here for masking
}

Related

Keep part of JSON as String with JacksonDeserializer

I want to keep a part of a JSON as String value.
As far as i know, there is no way with Annotations, but i could not find a way how to get the full Object/Array value as String.
There is a Workaround, which works, by reading it as an Object and instantly write it back as an String by using the ObjectMapper of Jackson.
You can imagine, this is a horrible solution for very big JSONs.
public class DeserializeTest {
private static ObjectMapper mapper;
public static void main(String[] args) throws IOException {
mapper = Jackson2ObjectMapperBuilder.json().build();
mapper.findAndRegisterModules();
SimpleModule module = new SimpleModule();
module.addDeserializer(TestClassWrapper.class, new TestDeserializer());
mapper.registerModule(module);
String json = "{\"name\":\"testprop\", \"data\":[{\"prop\":\"test\"},{\"prop\":\"test1\"},{\"prop\":\"test2\"}]}";
TestClassWrapper t = mapper.readValue(json, TestClassWrapper.class);
// later in program, when i know the expected class
TestClass o = unwrap(t, new TypeReference<ArrayList<Test2>>() {});
}
public static class TestClassWrapper {
String name;
String data;
// removed getter and setter
}
public static class TestClass {
String name;
List<Test2> data;
// removed getter and setter
}
public static class Test2 {
String prop;
// removed getter and setter
}
public static class TestDeserializer extends JsonDeserializer<TestClassWrapper> {
#Override
public TestClassWrapper deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
TestClassWrapper t = new TestClassWrapper();
String key = p.getCurrentName();
if (key == null) {
p.nextToken();
key = p.getCurrentName();
}
for (; key != null; key = p.nextFieldName()) {
p.nextToken();
switch (key) {
case "name":
t.name = p.getValueAsString();
break;
case "data":
// what i tried:
System.out.println(p.getText()); // [
System.out.println(p.getValueAsString()); // NULL
System.out.println(p.getCurrentValue()); //NULL
System.out.println(p.getCurrentToken()); // [ TOKEN
System.out.println(p.getParsingContext().getCurrentValue()); // NULL
System.out.println(p.getParsingContext().toString()); // [0]
System.out.println(p.getEmbeddedObject()); // NULL
System.out.println(p.getTextCharacters()); // [
try {
System.out.println(ctxt.readValue(p, String.class)); // MismatchedInputException
} catch (MismatchedInputException e){}
// The only way i could make it work.
// Parse to a object and write it back as string.
StringBuilder sb = new StringBuilder();
Iterator<Object> it = p.readValuesAs(Object.class);
while (it.hasNext()) {
sb.append(mapper.writeValueAsString(it.next()));
sb.append(it.hasNext() ? "," : "");
}
t.data = p.getCurrentToken() == JsonToken.END_ARRAY ? "[" + sb.toString() + "]" : sb.toString();
break;
}
}
return t;
}
}
public static TestClass unwrap(TestClassWrapper t, TypeReference targetClass) throws IOException {
TestClass o = new TestClass();
o.name = t.name;
o.data = mapper.readValue(t.data, targetClass);
return o;
}
}
How can i tell the JsonParser object, to just give me the String of the current value?
(For data this would be: "[{"prop":"test"}, {"prop":"test1"}, {"prop":"test2"}]")

Java working with .class

I am creating java app which will allow storing objects in database. What I want to do is generic implementation so it could load json and create java class from it. This is what a code should look like:
SomeClass someObject= data.getValue(SomeClass.class);
Lets say that data would be a json object. How should I implement getValue() method so it will allow me to create class from it. I don't want SomeClass to extend anything other then Object. I think that this should be done using generic classes but so far I have not worked with generic classes like this. Can you please point to a best way on how to acomplish this? Example code would be best.
Many thanks
You can consult the source code of Jackson library and look inside (or debug) the method BeanDeserializer#vanillaDeserialize(), there you'll find the loop which traverse through all json tokens, finds the corresponding fields and sets their values.
As a proof of concept, I've extracted part of the logic from Jacskson and wrapped it inside a naive (and fragile) object mapper and a naive (and fragile) json parser:
public static class NaiveObjectMapper {
private Map<String, Object> fieldsAndMethods;
private NaiveJsonParser parser;
public <T> T readValue(String content, Class<T> valueType) {
parser = new NaiveJsonParser(content);
try {
// aggregate all value type fields and methods inside a map
fieldsAndMethods = new HashMap<>();
for (Field field : valueType.getDeclaredFields()) {
fieldsAndMethods.put(field.getName(), field);
}
for (Method method : valueType.getMethods()) {
fieldsAndMethods.put(method.getName(), method);
}
// create an instance of value type by calling its default constructor
Constructor<T> constructor = valueType.getConstructor();
Object bean = constructor.newInstance(new Object[0]);
// loop through all json nodes
String propName;
while ((propName = parser.nextFieldName()) != null) {
// find the corresponding field
Field prop = (Field) fieldsAndMethods.get(propName);
// get and set field value
deserializeAndSet(prop, bean);
}
return (T) bean;
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
return null;
}
private void deserializeAndSet(Field prop, Object bean) {
Class<?> propType = prop.getType();
Method setter = (Method) fieldsAndMethods.get(getFieldSetterName(prop));
try {
if (propType.isPrimitive()) {
if (propType.getName().equals("int")) {
setter.invoke(bean, parser.getIntValue());
}
} else if (propType == String.class) {
setter.invoke(bean, parser.getTextValue());
}
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
private String getFieldSetterName(Field prop) {
String propName = prop.getName();
return "set" + propName.substring(0, 1).toUpperCase() + propName.substring(1);
}
}
class NaiveJsonParser {
String[] nodes;
int currentNodeIdx = -1;
String currentProperty;
String currentValueStr;
public NaiveJsonParser(String content) {
// split the content into 'property:value' nodes
nodes = content.replaceAll("[{}]", "").split(",");
}
public String nextFieldName() {
if ((++currentNodeIdx) >= nodes.length) {
return null;
}
String[] propertyAndValue = nodes[currentNodeIdx].split(":");
currentProperty = propertyAndValue[0].replace("\"", "").trim();
currentValueStr = propertyAndValue[1].replace("\"", "").trim();
return currentProperty;
}
public String getTextValue() {
return String.valueOf(currentValueStr);
}
public int getIntValue() {
return Integer.valueOf(currentValueStr).intValue();
}
}
public static class User {
private int id;
private String name;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
#Override
public String toString() {
return "id = " + id + ", name = \"" + name + "\"";
}
}
To see the deserialization in action run:
String json = "{\"id\":1, \"name\":\"jsmith\"}";
NaiveObjectMapper objectMapper = new NaiveObjectMapper();
User user = objectMapper.readValue(json, User.class);
System.out.println(user);
Or try online.
However I recommend not to reinvent the wheel and use Jackson and in case you need some custom actions you can use custom deserialization, see here and here.

Using GSON do not parse a field, only keep it with the json raw string

Using GSON in Java is there any annotation where I can indicate a field that it should keep it as a raw string even though it is an object. ?
Or What would be the easiest way to achieve this?
//This is the original
#SerializedName("perro")
public Perro perro
//This is what I want to achieve
#SerializedName("perro")
public String perro
So the result should be
perro = "{"Users":[{"Name":"firulais","Raza":"beagle"},{"Name":"Spike","Value":"Terrier"}]}"
The only way I found this to work was using
public JsonElement perro;
Based on #mrsegev's answer, here's a simpler version (in Kotlin) that works with arbitrary objects:
class RawJsonAdapter: TypeAdapter<String>() {
override fun write(out: JsonWriter?, value: String?) {
out?.jsonValue(value)
}
override fun read(reader: JsonReader?): String {
return JsonParser().parse(reader).toString()
}
}
This takes advantage of JsonWriter#jsonValue() which was added in https://github.com/google/gson/pull/667
Usage:
#JsonAdapter(RawJsonAdapter::class)
val fieldName: String? = null
Basically speaking, You need to create a custom gson TypeAdapter class and write the conversion login from Object to String yourself.
Then annotate the field indicating what TypeAdapter to use in order to read/write it using gson.
More details in this blog post: Gson TypeAdapter Example
Example: Prasing class object as a raw JSON string
public class StringTypeAdapter extends TypeAdapter<String> {
#Override
public void write(JsonWriter out, String value) throws IOException {
try {
JSONObject jsonObject = new JSONObject(value);
out.beginObject();
Iterator<String> iterator = jsonObject.keys();
while (iterator.hasNext()) {
String key = iterator.next();
String keyValue = jsonObject.getString(key);
out.name(key).value(keyValue);
}
out.endObject();
} catch (JSONException e) {
e.printStackTrace();
}
}
#Override
public String read(JsonReader in) throws IOException {
in.beginObject();
JSONObject jsonObject = new JSONObject();
while (in.hasNext()) {
final String name = in.nextName();
final String value = in.nextString();
try {
jsonObject.put(name, value);
} catch (JSONException e) {
e.printStackTrace();
}
}
in.endObject();
return jsonObject.toString();
}
}
Using the TypeAdapter:
#JsonAdapter(StringTypeAdapter.class)
private String someClass; // Lazy parsing this json
You should be able to use public JsonObject perro;
You can then call gson.toJson(perro) to get the String value.

How to create map of java POJO class/ Json String having primitive data?

I want to make a Map (String ,Object) like this
{AssessmentId=0, Physical_name='ram', Physical_height=20, Physical_weight=60}
from my Pojo Class - InitialAssessment
public class InitialAssessment {
private long AssessmentId;
private String physical_name;
private String physical_gender;
private int physical_height;
private float physical_weight;
// all getter And setter is Created here
}
without using any external Library like Gson etc.
You can use this approach:
public Map getMapFromPojo(InitialAssessment assessment) throws Exception {
Map<String, Object> map = new HashMap<>();
if (assessment != null) {
Method[] methods = assessment.getClass().getMethods();
for (Method method : methods) {
String name = method.getName();
if (name.startsWith("get") && !name.equalsIgnoreCase("getClass")) {
Object value = "";
try {
value = method.invoke(assessment);
map.put(name.substring(name.indexOf("get") + 3), value);
} catch (Exception e) {
e.printStackTrace();
}
}
}
return map;
}
return null;
}
It will give you map for pojo class like this:
Output:
{AssessmentId=0, Physical_name='ram', Physical_gender='Male' , Physical_height=20, Physical_weight=60}

How to extract array values within nested JSON data

I have JSON data in the following format:
{
"data": {
"id": 14810798216415,
"name": "crescentbahuman.com",
"is_organization": true,
"email_domains": [
"crescentbahuman.com"
]
}
}
I want to get the string in the "email_domains" field. I wrote the following code as my attempt:
JSONObject dataObject2= (JSONObject)jsonObject2.get("data");
long id = (long) dataObject2.get("id");
System.out.println("worksapce id is: " + id);
String name = (String) dataObject2.get("name");
System.out.println("The worksapce name is: " + name);
boolean is_organization = (boolean) dataObject2.get("is_organization");
System.out.println("The workspace is organization: " + is_organization);
JSONArray email_domains = (JSONArray) jsonObject2.get("email_domains");
Iterator<String> iterator = email_domains.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
In this code to get "email_domains" only, a JSON Array object is created that get data from the JSON object and then its iterator is used to get values from within the array. However, it throws a NullPointerException on this line:
Iterator<String> iterator = email_domains.iterator();
I am stuck due to this problem. Can anyone kindly suggest a solution?
If you are using the JSON library from http://www.json.org/java/, then you should not be using JSONObject.get() so frequently. The library has other methods to get specific types, such as getLong(), getJSONArray(), and so forth. For your case with the "email_domains" field, you should try:
JSONArray array = dataObject2.getJSONArray("email_domains");
String value = array.getString(0);
org.json.JSONArray email_domains = (org.json.JSONArray) json.get("email_domains");
int length = email_domains.length();
for(int i = length-1; i > 0; i--) {
org.json.JSONObject jsonData = (org.json.JSONObject) email_domains.get(i);
System.out.println(jsonData);
}
My solution? I hate to be someone to offer a solution in another library... but look into google collections and the Gson helper. It can turn your Json into a map for you, and then back to json again when you are done.
Map map = gson.fromJson(jsonText, Map.class);
JsonArray's can then be cast into List's
try this to fetch "email_domains"
JSONArray email_domains = ((JSONArray) jsonObject).get("email_domains");
or
JSONObject obj = new JSONObject(jsonObject.Tostring());
JSONArray email_domains = obj.optJSONArray("email_domains");
"email_address" is JSONArray so we need to fetch this like
JSONArray email_domains = (JSONArray) dataObject2.getJSONArray("email_domains");
email_domains.get(0); // this will return crescentbahuman.com
Use this implementation
import java.lang.reflect.Field;
import java.util.List;
import com.google.gson.ExclusionStrategy;
import com.google.gson.FieldAttributes;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
public class JsonConvertor {
private static GsonBuilder gsonBuilder;
private static Gson gson;
private JsonConvertor() {
}
public static Object fromJson(String json, Class clz)
{
gson=new Gson();
return gson.fromJson(json,clz);
}
public static String toJson(Object obj) {
gsonBuilder = new GsonBuilder();
gsonBuilder = gsonBuilder
.addSerializationExclusionStrategy(new CustomIclusionStrategy(
obj.getClass()));
gson = gsonBuilder.create();
String json = gson.toJson(obj);
return json;
}
}
class CustomIclusionStrategy implements ExclusionStrategy {
private Class classToIclude;
private Field[] declaredFields;
private List<FieldAttributes> fields;
public CustomIclusionStrategy(List<FieldAttributes> fields) {
this.fields = fields;
}
public CustomIclusionStrategy(Class classToIclude) {
this.classToIclude = classToIclude;
this.declaredFields=classToIclude.getDeclaredFields();
}
// called only if shouldSkipClass returns false
#Override
public boolean shouldSkipField(FieldAttributes f) {
try {
classToIclude.getSuperclass().getDeclaredField(f.getName());
System.out.println(f.getName());
return true;
} catch (Exception e) {
}
return false;
}
#Override
public boolean shouldSkipClass(Class<?> clazz) {
// if returns false shouldSkipField will be called, otherwise
//shouldSkipField will not be called
return false;
}
}
public class Org {
private Data data;
public Org(Data data) {
super();
this.data = data;
}
public Data getData() {
return data;
}
public void setData(Data data) {
this.data = data;
}
public String toJson()
{
return JsonConvertor.toJson(this);
}
public static void main(String[] args) {
String json="{\"data\": {\"id\":\"1\",\"name\":\"org1\",\"is_organization\":true,\"email_domains\": [\"email1\",\"email2\",\"email3\",\"email4\"]}}";
Org o=(Org) JsonConvertor.fromJson(json, Org.class);
System.out.println(o.getData().getEmail_domains());
}
}

Categories

Resources