├── src ├── test │ ├── resources │ │ ├── encoding │ │ │ └── messages_zh_TW.properties │ │ ├── arrays │ │ │ ├── arrays_as_value_in_array_elements.properties │ │ │ ├── multi_dim_array_in_path_simple_values.properties │ │ │ ├── multi_dim_array_in_path_object_values.properties │ │ │ └── mixin_types_in_array.properties │ │ ├── arrayCombinations.properties │ │ ├── order-of-properties.properties │ │ ├── resolvers.properties │ │ ├── primitiveTypes.properties │ │ ├── arraysMixinTypes.properties │ │ └── example.properties │ ├── java │ │ └── pl │ │ │ └── jalokim │ │ │ └── propertiestojson │ │ │ ├── domain │ │ │ ├── Address.java │ │ │ ├── Group.java │ │ │ ├── Booleans.java │ │ │ ├── Numbers.java │ │ │ ├── Insurance.java │ │ │ ├── MainComplexObject.java │ │ │ ├── ComplexObject.java │ │ │ ├── MainObject.java │ │ │ └── Man.java │ │ │ ├── helper │ │ │ ├── PropertyKeysOrderResolverForTest.java │ │ │ └── PropertiesWithInsertOrderTest.java │ │ │ ├── util │ │ │ ├── PropertiesToJsonConverterEncodingTest.java │ │ │ ├── JsonCheckerUtil.java │ │ │ ├── PropertiesToJsonConverterFilterTest.java │ │ │ ├── AbstractPropertiesToJsonConverterTest.java │ │ │ └── PropertiesToJsonConverterTest.java │ │ │ └── resolvers │ │ │ └── primitives │ │ │ ├── NumberJsonTypeResolverTest.java │ │ │ ├── custom │ │ │ ├── LocalDateToJsonTypeConverterTest.java │ │ │ ├── TextToLocalDateResolverTest.java │ │ │ └── LocalDateConvertersTest.java │ │ │ ├── PrimitiveJsonTypeResolverTest.java │ │ │ └── example │ │ │ ├── LocalDateTimeResolver.java │ │ │ └── LocalDateTimeResolverTest.java │ └── groovy │ │ └── pl │ │ └── jalokim │ │ └── propertiestojson │ │ ├── object │ │ └── ArrayJsonTypeTest.groovy │ │ ├── PropertyArrayHelperTest.groovy │ │ ├── JsonObjectsTraverseResolverTest.groovy │ │ ├── util │ │ ├── PropertiesToJsonConverterEmptyPropertiesTest.groovy │ │ ├── PropertiesToJsonConverterMixinObjectPropertiesTest.groovy │ │ └── PropertiesToJsonConverterSpockTest.groovy │ │ ├── path │ │ └── PathMetadataBuilderTest.groovy │ │ └── JsonObjectFieldsValidatorTest.groovy └── main │ └── java │ └── pl │ └── jalokim │ └── propertiestojson │ ├── AlgorithmType.java │ ├── path │ ├── PathParser.java │ ├── InsideBracketsParser.java │ ├── OuterParser.java │ ├── ParserContext.java │ ├── PathMetadataBuilder.java │ └── PathMetadata.java │ ├── object │ ├── NumberJsonType.java │ ├── BooleanJsonType.java │ ├── PrimitiveJsonType.java │ ├── StringJsonType.java │ ├── AbstractJsonType.java │ ├── MergableObject.java │ ├── JsonNullReferenceType.java │ ├── SkipJsonField.java │ ├── ObjectJsonType.java │ └── ArrayJsonType.java │ ├── resolvers │ ├── primitives │ │ ├── delegator │ │ │ ├── InvokedFromDelegator.java │ │ │ └── PrimitiveJsonTypeDelegatorResolver.java │ │ ├── adapter │ │ │ ├── InvokedFromAdapter.java │ │ │ └── PrimitiveJsonTypeResolverToNewApiAdapter.java │ │ ├── NumberJsonTypeResolver.java │ │ ├── StringJsonTypeResolver.java │ │ ├── BooleanJsonTypeResolver.java │ │ ├── CharacterJsonTypeResolver.java │ │ ├── EmptyStringJsonTypeResolver.java │ │ ├── string │ │ │ ├── TextToCharacterResolver.java │ │ │ ├── TextToStringResolver.java │ │ │ ├── TextToEmptyStringResolver.java │ │ │ ├── TextToBooleanResolver.java │ │ │ ├── TextToJsonNullReferenceResolver.java │ │ │ ├── TextToObjectResolver.java │ │ │ ├── TextToConcreteObjectResolver.java │ │ │ ├── TextToNumberResolver.java │ │ │ └── TextToElementsResolver.java │ │ ├── object │ │ │ ├── NumberToJsonTypeConverter.java │ │ │ ├── BooleanToJsonTypeConverter.java │ │ │ ├── CharacterToJsonTypeConverter.java │ │ │ ├── AbstractObjectToJsonTypeConverter.java │ │ │ ├── StringToJsonTypeConverter.java │ │ │ ├── NullToJsonTypeConverter.java │ │ │ ├── HasGenericType.java │ │ │ ├── ElementsToJsonTypeConverter.java │ │ │ ├── SuperObjectToJsonTypeConverter.java │ │ │ └── ObjectToJsonTypeConverter.java │ │ ├── JsonNullReferenceTypeResolver.java │ │ ├── PrimitiveArrayJsonTypeResolver.java │ │ ├── ObjectFromTextJsonTypeResolver.java │ │ ├── custom │ │ │ ├── TextToLocalDateResolver.java │ │ │ └── LocalDateToJsonTypeConverter.java │ │ ├── PrimitiveJsonTypeResolver.java │ │ └── utils │ │ │ └── JsonObjectHelper.java │ ├── transfer │ │ └── DataForResolve.java │ ├── JsonTypeResolver.java │ ├── ObjectJsonTypeResolver.java │ ├── ArrayJsonTypeResolver.java │ ├── hierarchy │ │ ├── JsonTypeResolversHierarchyResolver.java │ │ └── HierarchyClassResolver.java │ └── PrimitiveJsonTypesResolver.java │ ├── util │ ├── exception │ │ ├── ReadInputException.java │ │ ├── CannotOverrideFieldException.java │ │ ├── MergeObjectException.java │ │ └── ParsePropertiesException.java │ └── StringToJsonStringWrapper.java │ ├── exception │ └── NotLeafValueException.java │ ├── helper │ ├── PropertyKeysOrderResolver.java │ └── PropertiesWithInsertOrder.java │ ├── Constants.java │ ├── PropertyArrayHelper.java │ ├── JsonObjectFieldsValidator.java │ └── JsonObjectsTraverseResolver.java ├── .travis.yml ├── .github └── workflows │ └── maven.yml ├── .gitignore └── pom.xml /src/test/resources/encoding/messages_zh_TW.properties: -------------------------------------------------------------------------------- 1 | accettoCondizioni=我接受使用條款 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | 3 | matrix: 4 | include: 5 | - jdk: openjdk8 6 | 7 | script: 8 | - mvn clean install jacoco:report coveralls:report -------------------------------------------------------------------------------- /src/test/resources/arrays/arrays_as_value_in_array_elements.properties: -------------------------------------------------------------------------------- 1 | array[0]=["value1","value2"] 2 | array[1]=["value3","value4"] 3 | array[2]=["value5","value6"] 4 | -------------------------------------------------------------------------------- /src/main/java/pl/jalokim/propertiestojson/AlgorithmType.java: -------------------------------------------------------------------------------- 1 | package pl.jalokim.propertiestojson; 2 | 3 | public enum AlgorithmType { 4 | PRIMITIVE, ARRAY, OBJECT 5 | } 6 | -------------------------------------------------------------------------------- /src/test/resources/arrayCombinations.properties: -------------------------------------------------------------------------------- 1 | test={"test": 12} 2 | arraytexts=1,23.0,5,false,text 3 | jsonObject={"fieldName":2, "text":"textValue"} 4 | jsonArray=[123,1234,,""] -------------------------------------------------------------------------------- /src/test/resources/arrays/multi_dim_array_in_path_simple_values.properties: -------------------------------------------------------------------------------- 1 | array[0][0]=test00 2 | array[0][1]=test01 3 | array[0][2]=test02 4 | array[1][0]=test10 5 | array[1][1]=test11 -------------------------------------------------------------------------------- /src/main/java/pl/jalokim/propertiestojson/path/PathParser.java: -------------------------------------------------------------------------------- 1 | package pl.jalokim.propertiestojson.path; 2 | 3 | interface PathParser { 4 | 5 | void parseNextChar(ParserContext parserContext, char currentChar); 6 | } 7 | -------------------------------------------------------------------------------- /src/test/resources/order-of-properties.properties: -------------------------------------------------------------------------------- 1 | someField.nextField0.leaf1=1 2 | someField.nextField0.leaf2=2 3 | someField.nextField1.leaf1=1 4 | anotherField.nextField.leaf1=1 5 | someField.nextField0.leaf3=3 6 | anotherField.nextField.leaf2=2 7 | 0field=0 -------------------------------------------------------------------------------- /src/main/java/pl/jalokim/propertiestojson/object/NumberJsonType.java: -------------------------------------------------------------------------------- 1 | package pl.jalokim.propertiestojson.object; 2 | 3 | public class NumberJsonType extends PrimitiveJsonType { 4 | 5 | public NumberJsonType(Number value) { 6 | super(value); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/test/resources/resolvers.properties: -------------------------------------------------------------------------------- 1 | man.someArray=[true, false, "string", 12.0, "{"field":"fieldValue"}", null, null, normal text, {"field":"fieldValue"}] 2 | man.someArray2=true, false, "string", 12.0, "{"field":"fieldValue"}", null, null, normal text, {"field":"fieldValue"} -------------------------------------------------------------------------------- /src/main/java/pl/jalokim/propertiestojson/object/BooleanJsonType.java: -------------------------------------------------------------------------------- 1 | package pl.jalokim.propertiestojson.object; 2 | 3 | public class BooleanJsonType extends PrimitiveJsonType { 4 | 5 | public BooleanJsonType(Boolean value) { 6 | super(value); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/test/java/pl/jalokim/propertiestojson/domain/Address.java: -------------------------------------------------------------------------------- 1 | package pl.jalokim.propertiestojson.domain; 2 | 3 | import lombok.Getter; 4 | import lombok.Setter; 5 | 6 | @Getter 7 | @Setter 8 | public class Address { 9 | 10 | String city; 11 | String street; 12 | } 13 | -------------------------------------------------------------------------------- /src/test/java/pl/jalokim/propertiestojson/domain/Group.java: -------------------------------------------------------------------------------- 1 | package pl.jalokim.propertiestojson.domain; 2 | 3 | import lombok.Getter; 4 | import lombok.Setter; 5 | 6 | @Getter 7 | @Setter 8 | public class Group { 9 | 10 | private String type; 11 | private String name; 12 | } 13 | -------------------------------------------------------------------------------- /src/test/resources/primitiveTypes.properties: -------------------------------------------------------------------------------- 1 | complexObject.booleans.trueValue=True 2 | complexObject.booleans.falseValue=false 3 | complexObject.numbers.integerValue=11 4 | complexObject.numbers.doubleValue=11.0 5 | complexObject.text=text 6 | simpleText=text2 7 | complexObject.nullValue=null 8 | complexObject.empty= -------------------------------------------------------------------------------- /src/test/java/pl/jalokim/propertiestojson/domain/Booleans.java: -------------------------------------------------------------------------------- 1 | package pl.jalokim.propertiestojson.domain; 2 | 3 | import lombok.Getter; 4 | import lombok.Setter; 5 | 6 | @Getter 7 | @Setter 8 | public class Booleans { 9 | 10 | private Boolean trueValue; 11 | private Boolean falseValue; 12 | } 13 | -------------------------------------------------------------------------------- /src/test/java/pl/jalokim/propertiestojson/domain/Numbers.java: -------------------------------------------------------------------------------- 1 | package pl.jalokim.propertiestojson.domain; 2 | 3 | import lombok.Getter; 4 | import lombok.Setter; 5 | 6 | @Getter 7 | @Setter 8 | public class Numbers { 9 | 10 | private Integer integerValue; 11 | private Double doubleValue; 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/pl/jalokim/propertiestojson/resolvers/primitives/delegator/InvokedFromDelegator.java: -------------------------------------------------------------------------------- 1 | package pl.jalokim.propertiestojson.resolvers.primitives.delegator; 2 | 3 | /** 4 | * Invoked methods from old resolvers, which delegate methods call to new interfaces. 5 | */ 6 | public @interface InvokedFromDelegator { 7 | 8 | } 9 | -------------------------------------------------------------------------------- /src/test/java/pl/jalokim/propertiestojson/domain/Insurance.java: -------------------------------------------------------------------------------- 1 | package pl.jalokim.propertiestojson.domain; 2 | 3 | import lombok.Getter; 4 | import lombok.Setter; 5 | 6 | @Getter 7 | @Setter 8 | public class Insurance { 9 | 10 | private String type; 11 | private Double cost; 12 | private Boolean valid; 13 | } -------------------------------------------------------------------------------- /src/test/java/pl/jalokim/propertiestojson/domain/MainComplexObject.java: -------------------------------------------------------------------------------- 1 | package pl.jalokim.propertiestojson.domain; 2 | 3 | import lombok.Getter; 4 | import lombok.Setter; 5 | 6 | @Getter 7 | @Setter 8 | public class MainComplexObject { 9 | 10 | private ComplexObject complexObject; 11 | private String simpleText; 12 | } 13 | -------------------------------------------------------------------------------- /src/test/java/pl/jalokim/propertiestojson/domain/ComplexObject.java: -------------------------------------------------------------------------------- 1 | package pl.jalokim.propertiestojson.domain; 2 | 3 | import lombok.Getter; 4 | import lombok.Setter; 5 | 6 | @Getter 7 | @Setter 8 | public class ComplexObject { 9 | 10 | private Numbers numbers; 11 | private Booleans booleans; 12 | private String text; 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/pl/jalokim/propertiestojson/util/exception/ReadInputException.java: -------------------------------------------------------------------------------- 1 | package pl.jalokim.propertiestojson.util.exception; 2 | 3 | public class ReadInputException extends RuntimeException { 4 | 5 | private static final long serialVersionUID = 1L; 6 | 7 | public ReadInputException(Exception ex) { 8 | super(ex); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/pl/jalokim/propertiestojson/exception/NotLeafValueException.java: -------------------------------------------------------------------------------- 1 | package pl.jalokim.propertiestojson.exception; 2 | 3 | public class NotLeafValueException extends RuntimeException { 4 | 5 | private static final long serialVersionUID = 1L; 6 | 7 | public NotLeafValueException(String message) { 8 | super(message); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/test/java/pl/jalokim/propertiestojson/domain/MainObject.java: -------------------------------------------------------------------------------- 1 | package pl.jalokim.propertiestojson.domain; 2 | 3 | import lombok.Getter; 4 | import lombok.Setter; 5 | 6 | @Getter 7 | @Setter 8 | public class MainObject { 9 | 10 | private Man man; 11 | private Insurance insurance; 12 | private String field1; 13 | private String field2; 14 | } 15 | -------------------------------------------------------------------------------- /src/test/resources/arrays/multi_dim_array_in_path_object_values.properties: -------------------------------------------------------------------------------- 1 | arrayWitObjects[0][0].somefield.nextField=test00 2 | arrayWitObjects[0][1].somefield.nextField1=test01_field1 3 | arrayWitObjects[0][1].somefield.nextField2=test01_field2 4 | 5 | arrayWitObjects[1][0].somefield.nextField=test10 6 | arrayWitObjects[1][1].somefield.nextField1=test11_field1 7 | arrayWitObjects[1][1].somefield.nextField2=test11_field2 -------------------------------------------------------------------------------- /src/main/java/pl/jalokim/propertiestojson/resolvers/primitives/adapter/InvokedFromAdapter.java: -------------------------------------------------------------------------------- 1 | package pl.jalokim.propertiestojson.resolvers.primitives.adapter; 2 | 3 | /** 4 | * It means that annotated method will be used through PrimitiveJsonTypeResolverToNewApiAdapter to new interfaces. Old custom resolvers provided by user will be 5 | * called through adapter... 6 | */ 7 | public @interface InvokedFromAdapter { 8 | 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/pl/jalokim/propertiestojson/helper/PropertyKeysOrderResolver.java: -------------------------------------------------------------------------------- 1 | package pl.jalokim.propertiestojson.helper; 2 | 3 | import com.google.common.collect.Lists; 4 | import java.util.List; 5 | import java.util.Map; 6 | 7 | public class PropertyKeysOrderResolver { 8 | 9 | public List getKeysInExpectedOrder(Map properties) { 10 | return Lists.newArrayList(properties.keySet()); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/pl/jalokim/propertiestojson/object/PrimitiveJsonType.java: -------------------------------------------------------------------------------- 1 | package pl.jalokim.propertiestojson.object; 2 | 3 | public abstract class PrimitiveJsonType extends AbstractJsonType { 4 | 5 | protected T value; 6 | 7 | public PrimitiveJsonType(T value) { 8 | this.value = value; 9 | } 10 | 11 | @Override 12 | public String toStringJson() { 13 | return value.toString(); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/test/resources/arraysMixinTypes.properties: -------------------------------------------------------------------------------- 1 | array[0].name=Walter 2 | array[0].surname=White 3 | array[1].name=Freddy 4 | array[1].surname=Krueger 5 | array[1].nick=Freddy_k1 6 | array[2]=simpleString 7 | array[3]=true 8 | array[4]=1 9 | array[5]=1.1 10 | array[6].surname=Mick 11 | array[6].nick=Freddy_k1 12 | array[7].array[0]=test1 13 | array[7].array[1]=test2 14 | otherArray=test,boolean,true,11.1,test,1,false,FAlSE,FALSSE,"in quotation marks" -------------------------------------------------------------------------------- /src/main/java/pl/jalokim/propertiestojson/path/InsideBracketsParser.java: -------------------------------------------------------------------------------- 1 | package pl.jalokim.propertiestojson.path; 2 | 3 | class InsideBracketsParser implements PathParser { 4 | 5 | @Override 6 | public void parseNextChar(ParserContext parserContext, char currentChar) { 7 | parserContext.appendNextChar(currentChar); 8 | if (currentChar == ']') { 9 | parserContext.switchToOuterParser(); 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/pl/jalokim/propertiestojson/util/StringToJsonStringWrapper.java: -------------------------------------------------------------------------------- 1 | package pl.jalokim.propertiestojson.util; 2 | 3 | public final class StringToJsonStringWrapper { 4 | 5 | private static final String JSON_STRING_SCHEMA = "\"%s\""; 6 | 7 | private StringToJsonStringWrapper() { 8 | } 9 | 10 | public static String wrap(String textToWrap) { 11 | return String.format(JSON_STRING_SCHEMA, textToWrap.replace("\"", "\\\"")); 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/pl/jalokim/propertiestojson/object/StringJsonType.java: -------------------------------------------------------------------------------- 1 | package pl.jalokim.propertiestojson.object; 2 | 3 | import pl.jalokim.propertiestojson.util.StringToJsonStringWrapper; 4 | 5 | public class StringJsonType extends PrimitiveJsonType { 6 | 7 | public StringJsonType(String value) { 8 | super(value); 9 | } 10 | 11 | @Override 12 | public String toStringJson() { 13 | return StringToJsonStringWrapper.wrap(value); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/test/groovy/pl/jalokim/propertiestojson/object/ArrayJsonTypeTest.groovy: -------------------------------------------------------------------------------- 1 | package pl.jalokim.propertiestojson.object 2 | 3 | import spock.lang.Specification 4 | 5 | class ArrayJsonTypeTest extends Specification { 6 | 7 | def "put to 200 index"() { 8 | given: 9 | ArrayJsonType arrayJsonType = new ArrayJsonType() 10 | when: 11 | arrayJsonType.addElement(200, new StringJsonType("test"), null) 12 | then: 13 | arrayJsonType.getElement(200).toStringJson() == "\"test\"" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/test/java/pl/jalokim/propertiestojson/domain/Man.java: -------------------------------------------------------------------------------- 1 | package pl.jalokim.propertiestojson.domain; 2 | 3 | import java.util.List; 4 | import lombok.Getter; 5 | import lombok.Setter; 6 | 7 | @Getter 8 | @Setter 9 | public class Man { 10 | 11 | private String name; 12 | private String surname; 13 | private Address address; 14 | private List emails; 15 | private List children; 16 | private List groups; 17 | private List hoobies; 18 | private Insurance insurance; 19 | private Boolean married; 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/pl/jalokim/propertiestojson/path/OuterParser.java: -------------------------------------------------------------------------------- 1 | package pl.jalokim.propertiestojson.path; 2 | 3 | class OuterParser implements PathParser { 4 | 5 | @Override 6 | public void parseNextChar(ParserContext parserContext, char currentChar) { 7 | if (currentChar == '.') { 8 | parserContext.addNextPropertyPart(); 9 | } else { 10 | parserContext.appendNextChar(currentChar); 11 | if (currentChar == '[') { 12 | parserContext.switchToInsideBracketsParser(); 13 | } 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/pl/jalokim/propertiestojson/object/AbstractJsonType.java: -------------------------------------------------------------------------------- 1 | package pl.jalokim.propertiestojson.object; 2 | 3 | /** 4 | * It represents abstraction for json element. 5 | */ 6 | public abstract class AbstractJsonType { 7 | 8 | /** 9 | * This one simply concatenate to rest of json. Simply speaking when will not converted then will not create whole json correctly. 10 | * 11 | * @return string for part of json. 12 | */ 13 | public abstract String toStringJson(); 14 | 15 | @Override 16 | public final String toString() { 17 | return toStringJson(); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/test/resources/example.properties: -------------------------------------------------------------------------------- 1 | man.groups[0].type=Commercial 2 | man.groups[0].name=group1 3 | man.groups[1].type=Free 4 | man.groups[1].name=group2 5 | man.groups[2].type=Commercial 6 | man.groups[2].name=group3 7 | man.hoobies[0]=cars 8 | man.hoobies[1]=science 9 | man.hoobies[2]=women 10 | man.hoobies[3]=computers 11 | man.insurance.cost=126.543 12 | man.insurance.valid=true 13 | man.address.street=Jp2 14 | man.address.city=Waraw 15 | man.emails= example@gg.com ,example2@cc.com, example3@gg.com,example3@gg.com 16 | man.name=John 17 | man.surname=Surname 18 | man.married=false 19 | insurance.type=Medical 20 | insurance.cost=123 21 | field1=die2 22 | field2=die3 23 | -------------------------------------------------------------------------------- /src/main/java/pl/jalokim/propertiestojson/resolvers/primitives/NumberJsonTypeResolver.java: -------------------------------------------------------------------------------- 1 | package pl.jalokim.propertiestojson.resolvers.primitives; 2 | 3 | import pl.jalokim.propertiestojson.resolvers.primitives.delegator.PrimitiveJsonTypeDelegatorResolver; 4 | import pl.jalokim.propertiestojson.resolvers.primitives.object.NumberToJsonTypeConverter; 5 | import pl.jalokim.propertiestojson.resolvers.primitives.string.TextToNumberResolver; 6 | 7 | @Deprecated 8 | public class NumberJsonTypeResolver extends PrimitiveJsonTypeDelegatorResolver { 9 | 10 | public NumberJsonTypeResolver() { 11 | super(new TextToNumberResolver(), new NumberToJsonTypeConverter()); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/pl/jalokim/propertiestojson/resolvers/primitives/StringJsonTypeResolver.java: -------------------------------------------------------------------------------- 1 | package pl.jalokim.propertiestojson.resolvers.primitives; 2 | 3 | import pl.jalokim.propertiestojson.resolvers.primitives.delegator.PrimitiveJsonTypeDelegatorResolver; 4 | import pl.jalokim.propertiestojson.resolvers.primitives.object.StringToJsonTypeConverter; 5 | import pl.jalokim.propertiestojson.resolvers.primitives.string.TextToStringResolver; 6 | 7 | @Deprecated 8 | public class StringJsonTypeResolver extends PrimitiveJsonTypeDelegatorResolver { 9 | 10 | public StringJsonTypeResolver() { 11 | super(new TextToStringResolver(), new StringToJsonTypeConverter()); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/test/resources/arrays/mixin_types_in_array.properties: -------------------------------------------------------------------------------- 1 | someObject.array[0]=[[12, 13, 14, "test", true]] 2 | someObject.array[0][1]=[1, 1.1, true, "arrayValue1"] 3 | someObject.array[0][2]=[2, 2.2, true, "arrayValue2"] 4 | 5 | someObject.array[1]=test 6 | 7 | someObject.array[2][0]=test1 8 | someObject.array[2][1]=test2 9 | 10 | someObject.array[3][0]={"field": "value1"} 11 | someObject.array[3][1]={"field": "value2"} 12 | someObject.array[3][2]=simpleText 13 | someObject.array[3][3]=[1, 2] 14 | someObject.array[3][3][2]=[true, "boolean"] 15 | someObject.array[3][3][3]={"nextObjectField": "value_02"} 16 | 17 | someObject.array[4]={"some_field": "value3"} 18 | 19 | someObject.array[5]=["test", true] -------------------------------------------------------------------------------- /src/main/java/pl/jalokim/propertiestojson/Constants.java: -------------------------------------------------------------------------------- 1 | package pl.jalokim.propertiestojson; 2 | 3 | public final class Constants { 4 | 5 | public static final String NORMAL_DOT = "."; 6 | public static final String REGEX_DOT = "\\."; 7 | public static final String EMPTY_STRING = ""; 8 | public static final String NEW_LINE_SIGN = ","; 9 | public static final String SIMPLE_ARRAY_DELIMITER = ","; 10 | public static final String JSON_OBJECT_START = "{"; 11 | public static final String JSON_OBJECT_END = "}"; 12 | public static final String ARRAY_START_SIGN = "["; 13 | public static final String ARRAY_END_SIGN = "]"; 14 | 15 | private Constants() { 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/pl/jalokim/propertiestojson/resolvers/primitives/BooleanJsonTypeResolver.java: -------------------------------------------------------------------------------- 1 | package pl.jalokim.propertiestojson.resolvers.primitives; 2 | 3 | import pl.jalokim.propertiestojson.resolvers.primitives.delegator.PrimitiveJsonTypeDelegatorResolver; 4 | import pl.jalokim.propertiestojson.resolvers.primitives.object.BooleanToJsonTypeConverter; 5 | import pl.jalokim.propertiestojson.resolvers.primitives.string.TextToBooleanResolver; 6 | 7 | @Deprecated 8 | public class BooleanJsonTypeResolver extends PrimitiveJsonTypeDelegatorResolver { 9 | 10 | public BooleanJsonTypeResolver() { 11 | super(new TextToBooleanResolver(), new BooleanToJsonTypeConverter()); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/pl/jalokim/propertiestojson/resolvers/primitives/CharacterJsonTypeResolver.java: -------------------------------------------------------------------------------- 1 | package pl.jalokim.propertiestojson.resolvers.primitives; 2 | 3 | import pl.jalokim.propertiestojson.resolvers.primitives.delegator.PrimitiveJsonTypeDelegatorResolver; 4 | import pl.jalokim.propertiestojson.resolvers.primitives.object.CharacterToJsonTypeConverter; 5 | import pl.jalokim.propertiestojson.resolvers.primitives.string.TextToCharacterResolver; 6 | 7 | @Deprecated 8 | public class CharacterJsonTypeResolver extends PrimitiveJsonTypeDelegatorResolver { 9 | 10 | public CharacterJsonTypeResolver() { 11 | super(new TextToCharacterResolver(), new CharacterToJsonTypeConverter()); 12 | } 13 | } -------------------------------------------------------------------------------- /src/main/java/pl/jalokim/propertiestojson/resolvers/primitives/EmptyStringJsonTypeResolver.java: -------------------------------------------------------------------------------- 1 | package pl.jalokim.propertiestojson.resolvers.primitives; 2 | 3 | import pl.jalokim.propertiestojson.resolvers.primitives.delegator.PrimitiveJsonTypeDelegatorResolver; 4 | import pl.jalokim.propertiestojson.resolvers.primitives.object.StringToJsonTypeConverter; 5 | import pl.jalokim.propertiestojson.resolvers.primitives.string.TextToEmptyStringResolver; 6 | 7 | @Deprecated 8 | public class EmptyStringJsonTypeResolver extends PrimitiveJsonTypeDelegatorResolver { 9 | 10 | public EmptyStringJsonTypeResolver() { 11 | super(new TextToEmptyStringResolver(), new StringToJsonTypeConverter()); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/pl/jalokim/propertiestojson/resolvers/primitives/string/TextToCharacterResolver.java: -------------------------------------------------------------------------------- 1 | package pl.jalokim.propertiestojson.resolvers.primitives.string; 2 | 3 | import java.util.Optional; 4 | import pl.jalokim.propertiestojson.resolvers.PrimitiveJsonTypesResolver; 5 | 6 | public class TextToCharacterResolver implements TextToConcreteObjectResolver { 7 | 8 | @Override 9 | public Optional returnObjectWhenCanBeResolved(PrimitiveJsonTypesResolver primitiveJsonTypesResolver, String propertyValue, String propertyKey) { 10 | if (propertyValue.length() == 1) { 11 | return Optional.of(propertyValue.charAt(0)); 12 | } 13 | return Optional.empty(); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/pl/jalokim/propertiestojson/resolvers/primitives/string/TextToStringResolver.java: -------------------------------------------------------------------------------- 1 | package pl.jalokim.propertiestojson.resolvers.primitives.string; 2 | 3 | import java.util.Optional; 4 | import pl.jalokim.propertiestojson.resolvers.PrimitiveJsonTypesResolver; 5 | 6 | public class TextToStringResolver implements TextToConcreteObjectResolver { 7 | 8 | public static final TextToStringResolver TO_STRING_RESOLVER = new TextToStringResolver(); 9 | 10 | @Override 11 | public Optional returnObjectWhenCanBeResolved(PrimitiveJsonTypesResolver primitiveJsonTypesResolver, 12 | String propertyValue, 13 | String propertyKey) { 14 | return Optional.ofNullable(propertyValue); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/pl/jalokim/propertiestojson/resolvers/primitives/object/NumberToJsonTypeConverter.java: -------------------------------------------------------------------------------- 1 | package pl.jalokim.propertiestojson.resolvers.primitives.object; 2 | 3 | import java.util.Optional; 4 | import pl.jalokim.propertiestojson.object.AbstractJsonType; 5 | import pl.jalokim.propertiestojson.object.NumberJsonType; 6 | import pl.jalokim.propertiestojson.resolvers.PrimitiveJsonTypesResolver; 7 | 8 | public class NumberToJsonTypeConverter extends AbstractObjectToJsonTypeConverter { 9 | 10 | @Override 11 | public Optional convertToJsonTypeOrEmpty(PrimitiveJsonTypesResolver primitiveJsonTypesResolver, 12 | Number convertedValue, 13 | String propertyKey) { 14 | return Optional.of(new NumberJsonType(convertedValue)); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/pl/jalokim/propertiestojson/resolvers/primitives/object/BooleanToJsonTypeConverter.java: -------------------------------------------------------------------------------- 1 | package pl.jalokim.propertiestojson.resolvers.primitives.object; 2 | 3 | import java.util.Optional; 4 | import pl.jalokim.propertiestojson.object.AbstractJsonType; 5 | import pl.jalokim.propertiestojson.object.BooleanJsonType; 6 | import pl.jalokim.propertiestojson.resolvers.PrimitiveJsonTypesResolver; 7 | 8 | public class BooleanToJsonTypeConverter extends AbstractObjectToJsonTypeConverter { 9 | 10 | @Override 11 | public Optional convertToJsonTypeOrEmpty(PrimitiveJsonTypesResolver primitiveJsonTypesResolver, 12 | Boolean convertedValue, 13 | String propertyKey) { 14 | return Optional.of(new BooleanJsonType(convertedValue)); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/pl/jalokim/propertiestojson/resolvers/primitives/JsonNullReferenceTypeResolver.java: -------------------------------------------------------------------------------- 1 | package pl.jalokim.propertiestojson.resolvers.primitives; 2 | 3 | import pl.jalokim.propertiestojson.object.JsonNullReferenceType; 4 | import pl.jalokim.propertiestojson.resolvers.primitives.delegator.PrimitiveJsonTypeDelegatorResolver; 5 | import pl.jalokim.propertiestojson.resolvers.primitives.object.NullToJsonTypeConverter; 6 | import pl.jalokim.propertiestojson.resolvers.primitives.string.TextToJsonNullReferenceResolver; 7 | 8 | @Deprecated 9 | public class JsonNullReferenceTypeResolver extends PrimitiveJsonTypeDelegatorResolver { 10 | 11 | public JsonNullReferenceTypeResolver() { 12 | super(new TextToJsonNullReferenceResolver(), new NullToJsonTypeConverter()); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/pl/jalokim/propertiestojson/resolvers/primitives/object/CharacterToJsonTypeConverter.java: -------------------------------------------------------------------------------- 1 | package pl.jalokim.propertiestojson.resolvers.primitives.object; 2 | 3 | import java.util.Optional; 4 | import pl.jalokim.propertiestojson.object.AbstractJsonType; 5 | import pl.jalokim.propertiestojson.object.StringJsonType; 6 | import pl.jalokim.propertiestojson.resolvers.PrimitiveJsonTypesResolver; 7 | 8 | public class CharacterToJsonTypeConverter extends AbstractObjectToJsonTypeConverter { 9 | 10 | @Override 11 | public Optional convertToJsonTypeOrEmpty(PrimitiveJsonTypesResolver primitiveJsonTypesResolver, 12 | Character convertedValue, 13 | String propertyKey) { 14 | return Optional.of(new StringJsonType(convertedValue.toString())); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/pl/jalokim/propertiestojson/resolvers/primitives/object/AbstractObjectToJsonTypeConverter.java: -------------------------------------------------------------------------------- 1 | package pl.jalokim.propertiestojson.resolvers.primitives.object; 2 | 3 | import java.util.Collections; 4 | import java.util.List; 5 | 6 | @SuppressWarnings("unchecked") 7 | public abstract class AbstractObjectToJsonTypeConverter implements ObjectToJsonTypeConverter { 8 | 9 | protected final Class classesWhichCanResolved = resolveTypeOfResolver(); 10 | 11 | /** 12 | * Inform about that certain converter can convert from generic type. 13 | * 14 | * @return list of classes from which can convert to json object/element. 15 | */ 16 | @Override 17 | public List> getClassesWhichCanResolve() { 18 | return Collections.singletonList(classesWhichCanResolved); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /.github/workflows/maven.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build a Java project with Maven, and cache/restore any dependencies to improve the workflow execution time 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven 3 | 4 | name: Java CI with Maven 5 | 6 | on: 7 | push: 8 | branches: [ "develop" ] 9 | pull_request: 10 | branches: [ "develop" ] 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v3 19 | - name: Set up JDK 8 20 | uses: actions/setup-java@v3 21 | with: 22 | java-version: '8' 23 | distribution: 'adopt' 24 | cache: maven 25 | - name: Build with Maven 26 | run: mvn -B package --file pom.xml -Pcoverage jacoco:report coveralls:report 27 | -------------------------------------------------------------------------------- /src/main/java/pl/jalokim/propertiestojson/resolvers/primitives/string/TextToEmptyStringResolver.java: -------------------------------------------------------------------------------- 1 | package pl.jalokim.propertiestojson.resolvers.primitives.string; 2 | 3 | import java.util.Optional; 4 | import pl.jalokim.propertiestojson.resolvers.PrimitiveJsonTypesResolver; 5 | 6 | public class TextToEmptyStringResolver implements TextToConcreteObjectResolver { 7 | 8 | public static final TextToEmptyStringResolver EMPTY_TEXT_RESOLVER = new TextToEmptyStringResolver(); 9 | private static final String EMPTY_VALUE = ""; 10 | 11 | @Override 12 | public Optional returnObjectWhenCanBeResolved(PrimitiveJsonTypesResolver primitiveJsonTypesResolver, 13 | String propertyValue, 14 | String propertyKey) { 15 | String text = EMPTY_VALUE.equals(propertyValue) ? EMPTY_VALUE : null; 16 | return Optional.ofNullable(text); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/pl/jalokim/propertiestojson/resolvers/primitives/object/StringToJsonTypeConverter.java: -------------------------------------------------------------------------------- 1 | package pl.jalokim.propertiestojson.resolvers.primitives.object; 2 | 3 | import java.util.Optional; 4 | import pl.jalokim.propertiestojson.object.AbstractJsonType; 5 | import pl.jalokim.propertiestojson.object.StringJsonType; 6 | import pl.jalokim.propertiestojson.resolvers.PrimitiveJsonTypesResolver; 7 | 8 | public class StringToJsonTypeConverter extends AbstractObjectToJsonTypeConverter { 9 | 10 | public static final StringToJsonTypeConverter STRING_TO_JSON_RESOLVER = new StringToJsonTypeConverter(); 11 | 12 | @Override 13 | public Optional convertToJsonTypeOrEmpty(PrimitiveJsonTypesResolver primitiveJsonTypesResolver, 14 | String convertedValue, 15 | String propertyKey) { 16 | return Optional.of(new StringJsonType(convertedValue)); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/test/java/pl/jalokim/propertiestojson/helper/PropertyKeysOrderResolverForTest.java: -------------------------------------------------------------------------------- 1 | package pl.jalokim.propertiestojson.helper; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Arrays; 5 | import java.util.List; 6 | import java.util.Map; 7 | 8 | public class PropertyKeysOrderResolverForTest extends PropertyKeysOrderResolver { 9 | 10 | private List mockKeys = new ArrayList<>(); 11 | 12 | public void setUpMockKeys(String... keys) { 13 | mockKeys.addAll(Arrays.asList(keys)); 14 | } 15 | 16 | @Override 17 | public List getKeysInExpectedOrder(Map properties) { 18 | for (String mockKey : mockKeys) { 19 | if (properties.get(mockKey) == null) { 20 | throw new RuntimeException("cannot find key: " + mockKey + " in test keys indexing!"); 21 | } 22 | } 23 | return mockKeys; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/pl/jalokim/propertiestojson/resolvers/primitives/object/NullToJsonTypeConverter.java: -------------------------------------------------------------------------------- 1 | package pl.jalokim.propertiestojson.resolvers.primitives.object; 2 | 3 | import java.util.Optional; 4 | import pl.jalokim.propertiestojson.object.AbstractJsonType; 5 | import pl.jalokim.propertiestojson.object.JsonNullReferenceType; 6 | import pl.jalokim.propertiestojson.resolvers.PrimitiveJsonTypesResolver; 7 | 8 | public class NullToJsonTypeConverter extends AbstractObjectToJsonTypeConverter { 9 | 10 | public static final NullToJsonTypeConverter NULL_TO_JSON_RESOLVER = new NullToJsonTypeConverter(); 11 | 12 | @Override 13 | public Optional convertToJsonTypeOrEmpty(PrimitiveJsonTypesResolver primitiveJsonTypesResolver, 14 | JsonNullReferenceType convertedValue, 15 | String propertyKey) { 16 | return Optional.of(convertedValue); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/pl/jalokim/propertiestojson/object/MergableObject.java: -------------------------------------------------------------------------------- 1 | package pl.jalokim.propertiestojson.object; 2 | 3 | import pl.jalokim.propertiestojson.path.PathMetadata; 4 | import pl.jalokim.propertiestojson.util.exception.MergeObjectException; 5 | 6 | @SuppressWarnings("unchecked") 7 | public interface MergableObject { 8 | 9 | static void mergeObjectIfPossible(AbstractJsonType oldJsonElement, AbstractJsonType elementToAdd, PathMetadata currentPathMetadata) { 10 | MergableObject oldObject = (MergableObject) oldJsonElement; 11 | if (oldObject.getClass().isAssignableFrom(elementToAdd.getClass())) { 12 | oldObject.merge(elementToAdd, currentPathMetadata); 13 | } else { 14 | throw new MergeObjectException(oldJsonElement, elementToAdd, currentPathMetadata); 15 | } 16 | } 17 | 18 | void merge(T mergeWith, PathMetadata currentPathMetadata); 19 | } 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | 3 | ###################### 4 | # Eclipse 5 | ###################### 6 | *.pydevproject 7 | .project 8 | .metadata 9 | /bin/** 10 | /tmp/** 11 | /tmp/**/* 12 | *.tmp 13 | *.bak 14 | *.swp 15 | *~.nib 16 | local.properties 17 | .classpath 18 | .settings/** 19 | .loadpath 20 | /src/main/resources/rebel.xml 21 | 22 | # External tool builders 23 | .externalToolBuilders/** 24 | 25 | # Locally stored "Eclipse launch configurations" 26 | *.launch 27 | 28 | # CDT-specific 29 | .cproject 30 | 31 | # PDT-specific 32 | .buildpath 33 | 34 | ###################### 35 | # Intellij 36 | ###################### 37 | .idea/** 38 | *.iml 39 | *.iws 40 | *.ipr 41 | *.ids 42 | *.orig 43 | 44 | *.class 45 | 46 | # Mobile Tools for Java (J2ME) 47 | .mtj.tmp/ 48 | 49 | # Package Files # 50 | *.jar 51 | *.war 52 | *.ear 53 | 54 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 55 | hs_err_pid* 56 | pom.xml.versionsBackup 57 | .flattened-pom.xml 58 | 59 | -------------------------------------------------------------------------------- /src/main/java/pl/jalokim/propertiestojson/resolvers/primitives/string/TextToBooleanResolver.java: -------------------------------------------------------------------------------- 1 | package pl.jalokim.propertiestojson.resolvers.primitives.string; 2 | 3 | import java.util.Optional; 4 | import pl.jalokim.propertiestojson.resolvers.PrimitiveJsonTypesResolver; 5 | 6 | public class TextToBooleanResolver implements TextToConcreteObjectResolver { 7 | 8 | private static final String TRUE = "true"; 9 | private static final String FALSE = "false"; 10 | 11 | private static Boolean getBoolean(String value) { 12 | return Boolean.valueOf(value); 13 | } 14 | 15 | @Override 16 | public Optional returnObjectWhenCanBeResolved(PrimitiveJsonTypesResolver primitiveJsonTypesResolver, String propertyValue, String propertyKey) { 17 | if (TRUE.equalsIgnoreCase(propertyValue) || FALSE.equalsIgnoreCase(propertyValue)) { 18 | return Optional.of(getBoolean(propertyValue)); 19 | } 20 | return Optional.empty(); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/pl/jalokim/propertiestojson/util/exception/CannotOverrideFieldException.java: -------------------------------------------------------------------------------- 1 | package pl.jalokim.propertiestojson.util.exception; 2 | 3 | import com.google.common.annotations.VisibleForTesting; 4 | import pl.jalokim.propertiestojson.object.AbstractJsonType; 5 | 6 | public class CannotOverrideFieldException extends RuntimeException { 7 | 8 | private static final long serialVersionUID = 1L; 9 | 10 | private static final String CANNOT_OVERRIDE_VALUE = "Cannot override value at path: '%s', current value is: '%s', problematic property key: '%s'"; 11 | 12 | public CannotOverrideFieldException(String currentPath, AbstractJsonType currentValue, String propertyKey) { 13 | this(currentPath, currentValue.toString(), propertyKey); 14 | } 15 | 16 | @VisibleForTesting 17 | public CannotOverrideFieldException(String currentPath, String currentValue, String propertyKey) { 18 | super(String.format(CANNOT_OVERRIDE_VALUE, currentPath, currentValue, propertyKey)); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/pl/jalokim/propertiestojson/resolvers/primitives/string/TextToJsonNullReferenceResolver.java: -------------------------------------------------------------------------------- 1 | package pl.jalokim.propertiestojson.resolvers.primitives.string; 2 | 3 | import static pl.jalokim.propertiestojson.object.JsonNullReferenceType.NULL_OBJECT; 4 | import static pl.jalokim.propertiestojson.object.JsonNullReferenceType.NULL_VALUE; 5 | 6 | import java.util.Optional; 7 | import pl.jalokim.propertiestojson.resolvers.PrimitiveJsonTypesResolver; 8 | 9 | public class TextToJsonNullReferenceResolver implements TextToConcreteObjectResolver { 10 | 11 | public static final TextToJsonNullReferenceResolver TEXT_TO_NULL_JSON_RESOLVER = new TextToJsonNullReferenceResolver(); 12 | 13 | @Override 14 | public Optional returnObjectWhenCanBeResolved(PrimitiveJsonTypesResolver primitiveJsonTypesResolver, String propertyValue, String propertyKey) { 15 | if (propertyValue == null || propertyValue.equals(NULL_VALUE)) { 16 | return Optional.of(NULL_OBJECT); 17 | } 18 | return Optional.empty(); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/test/java/pl/jalokim/propertiestojson/util/PropertiesToJsonConverterEncodingTest.java: -------------------------------------------------------------------------------- 1 | package pl.jalokim.propertiestojson.util; 2 | 3 | import java.nio.charset.StandardCharsets; 4 | import org.assertj.core.api.Assertions; 5 | import org.junit.Test; 6 | 7 | public class PropertiesToJsonConverterEncodingTest { 8 | 9 | @Test 10 | public void shouldManageChineseCharacters() { 11 | String json = PropertiesToJsonConverterBuilder.builder().charset(StandardCharsets.UTF_8).build() 12 | .convertPropertiesFromFileToJson("src/test/resources/encoding/messages_zh_TW.properties"); 13 | Assertions.assertThat(json).contains("我接受使用條款"); 14 | } 15 | 16 | @Test 17 | public void shouldNotManageChineseCharactersDueToDifferentEncoding() { 18 | String json = PropertiesToJsonConverterBuilder.builder().charset(StandardCharsets.ISO_8859_1).build() 19 | .convertPropertiesFromFileToJson("src/test/resources/encoding/messages_zh_TW.properties"); 20 | Assertions.assertThat(json).doesNotContain("我接受使用條款"); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/test/java/pl/jalokim/propertiestojson/resolvers/primitives/NumberJsonTypeResolverTest.java: -------------------------------------------------------------------------------- 1 | package pl.jalokim.propertiestojson.resolvers.primitives; 2 | 3 | 4 | import static org.assertj.core.api.Assertions.assertThat; 5 | import static pl.jalokim.utils.reflection.InvokableReflectionUtils.invokeMethod; 6 | 7 | import java.util.Arrays; 8 | import java.util.Optional; 9 | import org.junit.Test; 10 | import pl.jalokim.propertiestojson.resolvers.PrimitiveJsonTypesResolver; 11 | 12 | public class NumberJsonTypeResolverTest { 13 | 14 | @Test 15 | public void test_01_WillNotConvertAsNumber() { 16 | // given 17 | NumberJsonTypeResolver numberJsonTypeResolver = new NumberJsonTypeResolver(); 18 | // when 19 | Optional numberOpt = invokeMethod(numberJsonTypeResolver, "returnConcreteValueWhenCanBeResolved", 20 | Arrays.asList(PrimitiveJsonTypesResolver.class, String.class, String.class), 21 | Arrays.asList(null, "01", "test")); 22 | // then 23 | assertThat(numberOpt.isPresent()).isFalse(); 24 | } 25 | 26 | } -------------------------------------------------------------------------------- /src/main/java/pl/jalokim/propertiestojson/object/JsonNullReferenceType.java: -------------------------------------------------------------------------------- 1 | package pl.jalokim.propertiestojson.object; 2 | 3 | import pl.jalokim.propertiestojson.resolvers.PrimitiveJsonTypesResolver; 4 | import pl.jalokim.propertiestojson.resolvers.primitives.object.ObjectToJsonTypeConverter; 5 | import pl.jalokim.propertiestojson.resolvers.primitives.string.TextToConcreteObjectResolver; 6 | 7 | /** 8 | * This is object for notify that given reference will be converted as null in json. It can be returned by {@link 9 | * ObjectToJsonTypeConverter#convertToJsonTypeOrEmpty(PrimitiveJsonTypesResolver, Object, String)} and can be returned by {@link 10 | * TextToConcreteObjectResolver#returnObjectWhenCanBeResolved(PrimitiveJsonTypesResolver, String, String)} 11 | */ 12 | public final class JsonNullReferenceType extends AbstractJsonType { 13 | 14 | public static final JsonNullReferenceType NULL_OBJECT = new JsonNullReferenceType(); 15 | 16 | public static final String NULL_VALUE = "null"; 17 | 18 | @Override 19 | public String toStringJson() { 20 | return NULL_VALUE; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/pl/jalokim/propertiestojson/object/SkipJsonField.java: -------------------------------------------------------------------------------- 1 | package pl.jalokim.propertiestojson.object; 2 | 3 | import pl.jalokim.propertiestojson.resolvers.PrimitiveJsonTypesResolver; 4 | import pl.jalokim.propertiestojson.resolvers.primitives.object.ObjectToJsonTypeConverter; 5 | import pl.jalokim.propertiestojson.resolvers.primitives.string.TextToConcreteObjectResolver; 6 | 7 | /** 8 | * Dummy object for notify that field with that value will not be added to json. Will not go to next resolver or converter. It can be returned by {@link 9 | * ObjectToJsonTypeConverter#convertToJsonTypeOrEmpty(PrimitiveJsonTypesResolver, Object, String)} and can be returned by {@link 10 | * TextToConcreteObjectResolver#returnObjectWhenCanBeResolved(PrimitiveJsonTypesResolver, String, String)} 11 | */ 12 | public final class SkipJsonField extends AbstractJsonType { 13 | 14 | public static final SkipJsonField SKIP_JSON_FIELD = new SkipJsonField(); 15 | 16 | private SkipJsonField() { 17 | 18 | } 19 | 20 | @Override 21 | public String toStringJson() { 22 | throw new UnsupportedOperationException("This is not normal implementation of AbstractJsonType"); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/pl/jalokim/propertiestojson/util/exception/MergeObjectException.java: -------------------------------------------------------------------------------- 1 | package pl.jalokim.propertiestojson.util.exception; 2 | 3 | import static java.lang.String.format; 4 | 5 | import com.google.common.annotations.VisibleForTesting; 6 | import pl.jalokim.propertiestojson.object.AbstractJsonType; 7 | import pl.jalokim.propertiestojson.path.PathMetadata; 8 | 9 | public class MergeObjectException extends RuntimeException { 10 | 11 | private static final long serialVersionUID = 1L; 12 | 13 | public MergeObjectException(AbstractJsonType oldJsonElement, AbstractJsonType elementToAdd, PathMetadata currentPathMetadata) { 14 | this(oldJsonElement.toStringJson(), elementToAdd.toStringJson(), currentPathMetadata); 15 | } 16 | 17 | @VisibleForTesting 18 | public MergeObjectException(String oldJsonElementValue, String elementToAddValue, PathMetadata currentPathMetadata) { 19 | super(format("Cannot merge objects with different types:%n Old object: %s%n New object: %s%n problematic key: '%s'%n with value: %s", 20 | oldJsonElementValue, elementToAddValue, currentPathMetadata.getOriginalPropertyKey(), currentPathMetadata.getRawValue())); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/test/groovy/pl/jalokim/propertiestojson/PropertyArrayHelperTest.groovy: -------------------------------------------------------------------------------- 1 | package pl.jalokim.propertiestojson 2 | 3 | import spock.lang.Specification 4 | import spock.lang.Unroll 5 | 6 | class PropertyArrayHelperTest extends Specification { 7 | 8 | def "get expected name from array field"(String arrayFieldName, String expectedArrayName) { 9 | when: 10 | String result = PropertyArrayHelper.getNameFromArray(arrayFieldName) 11 | then: 12 | expectedArrayName == result 13 | where: 14 | arrayFieldName | expectedArrayName 15 | "array [12] [12]" | "array" 16 | "array [12]" | "array" 17 | "array_test !@# [12]" | "array_test !@#" 18 | } 19 | 20 | @Unroll 21 | def "get expected indexes from array fields"(String arrayFieldName, int[] expectedIndexes) { 22 | when: 23 | def resultIndexes = PropertyArrayHelper.getIndexesFromArrayField(arrayFieldName) 24 | then: 25 | resultIndexes == expectedIndexes 26 | where: 27 | arrayFieldName | expectedIndexes 28 | "arrayField [12] [ 13]" | [12, 13] 29 | "arrayField[_] [11]" | [11] 30 | "arrayField [12] [ 13] [ 11 ] " | [12, 13, 11] 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/test/groovy/pl/jalokim/propertiestojson/JsonObjectsTraverseResolverTest.groovy: -------------------------------------------------------------------------------- 1 | package pl.jalokim.propertiestojson 2 | 3 | import pl.jalokim.propertiestojson.path.PathMetadata 4 | import spock.lang.Specification 5 | import spock.lang.Unroll 6 | 7 | class JsonObjectsTraverseResolverTest extends Specification { 8 | 9 | @Unroll 10 | def "all given field after test has expected result"(String arrayFieldName, boolean expected) { 11 | when: 12 | PathMetadata pathMetadata = new PathMetadata() 13 | pathMetadata.fieldName = arrayFieldName 14 | boolean result = pathMetadata.isArrayField() 15 | then: 16 | result == expected 17 | where: 18 | arrayFieldName | expected 19 | "array_name[12][13]" | true 20 | "array_name[12][13][0]" | true 21 | "array_name[12][13][11]" | true 22 | "array_name[12]" | true 23 | "array_name[12][a]" | false 24 | "array_name[12][12]a" | false 25 | "array_name[0][01]" | false 26 | "array_name[01][12]" | true 27 | "array_[12]name" | false 28 | "array_[]name" | false 29 | "array_[12name" | false 30 | "array]name" | false 31 | "array[name]" | false 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/pl/jalokim/propertiestojson/path/ParserContext.java: -------------------------------------------------------------------------------- 1 | package pl.jalokim.propertiestojson.path; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | class ParserContext { 7 | 8 | private static final PathParser OUTER_PARSER = new OuterParser(); 9 | private static final PathParser INSIDE_BRACKETS_PARSER = new InsideBracketsParser(); 10 | 11 | private final List propertiesParts = new ArrayList<>(); 12 | private PathParser currentParser = new OuterParser(); 13 | private StringBuilder currentWordBuilder = new StringBuilder(); 14 | 15 | void switchToOuterParser() { 16 | currentParser = OUTER_PARSER; 17 | } 18 | 19 | void switchToInsideBracketsParser() { 20 | currentParser = INSIDE_BRACKETS_PARSER; 21 | } 22 | 23 | void addNextPropertyPart() { 24 | propertiesParts.add(currentWordBuilder.toString()); 25 | currentWordBuilder = new StringBuilder(); 26 | } 27 | 28 | void appendNextChar(char nextChar) { 29 | currentWordBuilder.append(nextChar); 30 | } 31 | 32 | void parseNextChar(char nextChar) { 33 | this.currentParser.parseNextChar(this, nextChar); 34 | } 35 | 36 | List getPropertiesParts() { 37 | addNextPropertyPart(); 38 | return propertiesParts; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/pl/jalokim/propertiestojson/resolvers/transfer/DataForResolve.java: -------------------------------------------------------------------------------- 1 | package pl.jalokim.propertiestojson.resolvers.transfer; 2 | 3 | import java.util.Map; 4 | import pl.jalokim.propertiestojson.object.ObjectJsonType; 5 | import pl.jalokim.propertiestojson.path.PathMetadata; 6 | 7 | public class DataForResolve { 8 | 9 | private final Map properties; 10 | private final String propertyKey; 11 | private final ObjectJsonType currentObjectJsonType; 12 | private final PathMetadata currentPathMetaData; 13 | 14 | public DataForResolve(Map properties, String propertyKey, ObjectJsonType currentObjectJsonType, PathMetadata currentPathMetaData) { 15 | this.properties = properties; 16 | this.propertyKey = propertyKey; 17 | this.currentObjectJsonType = currentObjectJsonType; 18 | this.currentPathMetaData = currentPathMetaData; 19 | } 20 | 21 | public Map getProperties() { 22 | return properties; 23 | } 24 | 25 | public String getPropertiesKey() { 26 | return propertyKey; 27 | } 28 | 29 | public ObjectJsonType getCurrentObjectJsonType() { 30 | return currentObjectJsonType; 31 | } 32 | 33 | public PathMetadata getCurrentPathMetaData() { 34 | return currentPathMetaData; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/pl/jalokim/propertiestojson/resolvers/primitives/PrimitiveArrayJsonTypeResolver.java: -------------------------------------------------------------------------------- 1 | package pl.jalokim.propertiestojson.resolvers.primitives; 2 | 3 | import java.util.Collection; 4 | import pl.jalokim.propertiestojson.resolvers.primitives.delegator.PrimitiveJsonTypeDelegatorResolver; 5 | import pl.jalokim.propertiestojson.resolvers.primitives.object.ElementsToJsonTypeConverter; 6 | import pl.jalokim.propertiestojson.resolvers.primitives.string.TextToElementsResolver; 7 | 8 | /** 9 | * When given text contains ',' or text starts with '[' and ends with ']' in text then it tries split by comma and remove '[]' signs and then every separated 10 | * text tries convert to json value. It will try resolve every types by provided resolvers 11 | * in {@link pl.jalokim.propertiestojson.util.PropertiesToJsonConverter#PropertiesToJsonConverter(PrimitiveJsonTypeResolver...)} 12 | */ 13 | @Deprecated 14 | public class PrimitiveArrayJsonTypeResolver extends PrimitiveJsonTypeDelegatorResolver> { 15 | 16 | public PrimitiveArrayJsonTypeResolver() { 17 | super(new TextToElementsResolver(), new ElementsToJsonTypeConverter()); 18 | } 19 | 20 | public PrimitiveArrayJsonTypeResolver(boolean resolveTypeOfEachElement) { 21 | super(new TextToElementsResolver(resolveTypeOfEachElement), new ElementsToJsonTypeConverter()); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/pl/jalokim/propertiestojson/resolvers/JsonTypeResolver.java: -------------------------------------------------------------------------------- 1 | package pl.jalokim.propertiestojson.resolvers; 2 | 3 | import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; 4 | import java.util.Map; 5 | import pl.jalokim.propertiestojson.object.ArrayJsonType; 6 | import pl.jalokim.propertiestojson.object.ObjectJsonType; 7 | import pl.jalokim.propertiestojson.path.PathMetadata; 8 | import pl.jalokim.propertiestojson.resolvers.transfer.DataForResolve; 9 | 10 | @SuppressFBWarnings("URF_UNREAD_PUBLIC_OR_PROTECTED_FIELD") 11 | public abstract class JsonTypeResolver { 12 | 13 | protected Map properties; 14 | protected String propertyKey; 15 | protected ObjectJsonType currentObjectJsonType; 16 | 17 | protected ArrayJsonType getArrayJsonWhenIsValid(PathMetadata currentPathMetaData) { 18 | return (ArrayJsonType) currentObjectJsonType.getField(currentPathMetaData.getFieldName()); 19 | } 20 | 21 | public abstract ObjectJsonType traverse(PathMetadata currentPathMetaData); 22 | 23 | public final ObjectJsonType traverseOnObjectAndInitByField(DataForResolve dataForResolve) { 24 | properties = dataForResolve.getProperties(); 25 | propertyKey = dataForResolve.getPropertiesKey(); 26 | currentObjectJsonType = dataForResolve.getCurrentObjectJsonType(); 27 | return traverse(dataForResolve.getCurrentPathMetaData()); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/pl/jalokim/propertiestojson/path/PathMetadataBuilder.java: -------------------------------------------------------------------------------- 1 | package pl.jalokim.propertiestojson.path; 2 | 3 | import com.google.common.annotations.VisibleForTesting; 4 | import java.util.List; 5 | 6 | public final class PathMetadataBuilder { 7 | 8 | private PathMetadataBuilder() { 9 | } 10 | 11 | public static PathMetadata createRootPathMetaData(String propertyKey) { 12 | List fields = getPropertyParts(propertyKey); 13 | PathMetadata currentPathMetadata = null; 14 | 15 | for (String field : fields) { 16 | PathMetadata nextPathMetadata = new PathMetadata(propertyKey); 17 | nextPathMetadata.setParent(currentPathMetadata); 18 | nextPathMetadata.setFieldName(field); 19 | nextPathMetadata.setOriginalFieldName(field); 20 | 21 | if (currentPathMetadata != null) { 22 | currentPathMetadata.setChild(nextPathMetadata); 23 | } 24 | currentPathMetadata = nextPathMetadata; 25 | } 26 | return currentPathMetadata.getRoot(); 27 | } 28 | 29 | @VisibleForTesting 30 | static List getPropertyParts(String property) { 31 | char[] chars = property.toCharArray(); 32 | ParserContext parserContext = new ParserContext(); 33 | for (char currentChar : chars) { 34 | parserContext.parseNextChar(currentChar); 35 | } 36 | return parserContext.getPropertiesParts(); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/pl/jalokim/propertiestojson/resolvers/primitives/string/TextToObjectResolver.java: -------------------------------------------------------------------------------- 1 | package pl.jalokim.propertiestojson.resolvers.primitives.string; 2 | 3 | import static pl.jalokim.propertiestojson.resolvers.primitives.utils.JsonObjectHelper.convertToAbstractJsonType; 4 | import static pl.jalokim.propertiestojson.resolvers.primitives.utils.JsonObjectHelper.hasJsonArraySignature; 5 | import static pl.jalokim.propertiestojson.resolvers.primitives.utils.JsonObjectHelper.hasJsonObjectSignature; 6 | import static pl.jalokim.propertiestojson.resolvers.primitives.utils.JsonObjectHelper.toJsonElement; 7 | 8 | import java.util.Optional; 9 | import pl.jalokim.propertiestojson.object.AbstractJsonType; 10 | import pl.jalokim.propertiestojson.resolvers.PrimitiveJsonTypesResolver; 11 | 12 | public class TextToObjectResolver implements TextToConcreteObjectResolver { 13 | 14 | @Override 15 | public Optional returnObjectWhenCanBeResolved(PrimitiveJsonTypesResolver primitiveJsonTypesResolver, String propertyValue, 16 | String propertyKey) { 17 | if (hasJsonObjectSignature(propertyValue) || hasJsonArraySignature(propertyValue)) { 18 | try { 19 | return Optional.ofNullable(convertToAbstractJsonType(toJsonElement(propertyValue), propertyKey)); 20 | } catch (Exception exception) { 21 | return Optional.empty(); 22 | } 23 | } 24 | return Optional.empty(); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/pl/jalokim/propertiestojson/resolvers/primitives/object/HasGenericType.java: -------------------------------------------------------------------------------- 1 | package pl.jalokim.propertiestojson.resolvers.primitives.object; 2 | 3 | import java.lang.reflect.ParameterizedType; 4 | import pl.jalokim.propertiestojson.resolvers.primitives.adapter.InvokedFromAdapter; 5 | import pl.jalokim.propertiestojson.resolvers.primitives.delegator.InvokedFromDelegator; 6 | import pl.jalokim.propertiestojson.util.exception.ParsePropertiesException; 7 | 8 | public interface HasGenericType { 9 | 10 | @SuppressWarnings("unchecked") 11 | @InvokedFromAdapter 12 | @InvokedFromDelegator 13 | default Class resolveTypeOfResolver() { 14 | Class currentClass = getClass(); 15 | while (currentClass != null) { 16 | try { 17 | return (Class) ((ParameterizedType) currentClass 18 | .getGenericSuperclass()).getActualTypeArguments()[0]; 19 | } catch (Exception ccx) { 20 | currentClass = currentClass.getSuperclass(); 21 | } 22 | } 23 | throw new ParsePropertiesException("Cannot find generic type for resolver: " + getClass() 24 | + " You can resolve it by one of below:" 25 | + "\n 1. override method resolveTypeOfResolver() for provide explicit class type " 26 | + "\n 2. add generic type during extension of PrimitiveJsonTypeResolver " 27 | + "'class " + getClass().getSimpleName() + " extends PrimitiveJsonTypeResolver'"); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/pl/jalokim/propertiestojson/resolvers/primitives/string/TextToConcreteObjectResolver.java: -------------------------------------------------------------------------------- 1 | package pl.jalokim.propertiestojson.resolvers.primitives.string; 2 | 3 | import java.util.Optional; 4 | import pl.jalokim.propertiestojson.resolvers.PrimitiveJsonTypesResolver; 5 | 6 | public interface TextToConcreteObjectResolver { 7 | 8 | default Optional returnConvertedValueForClearedText(PrimitiveJsonTypesResolver primitiveJsonTypesResolver, String propertyValue, String propertyKey) { 9 | return returnObjectWhenCanBeResolved(primitiveJsonTypesResolver, 10 | propertyValue == null ? null : propertyValue.trim(), propertyKey); 11 | } 12 | 13 | /** 14 | * This method will be called in first phase processing step (from raw text to some object) if your condition is met then return Optional of concrete value 15 | * of Object. if it doesn't meet its condition then return Optional.empty() for allow go to others type resolver in order. This will be called only for read 16 | * properties from Map<String,String>, File with properties, InputStream with properties 17 | * 18 | * @param primitiveJsonTypesResolver primitiveJsonTypesResolver 19 | * @param propertyValue currently processing property value 20 | * @param propertyKey currently processing property key 21 | * @return optional value 22 | */ 23 | Optional returnObjectWhenCanBeResolved(PrimitiveJsonTypesResolver primitiveJsonTypesResolver, 24 | String propertyValue, 25 | String propertyKey); 26 | } 27 | -------------------------------------------------------------------------------- /src/test/groovy/pl/jalokim/propertiestojson/util/PropertiesToJsonConverterEmptyPropertiesTest.groovy: -------------------------------------------------------------------------------- 1 | package pl.jalokim.propertiestojson.util 2 | 3 | import groovy.json.JsonSlurper 4 | import spock.lang.Specification 5 | 6 | class PropertiesToJsonConverterEmptyPropertiesTest extends Specification { 7 | 8 | def "json has empty string for empty property and null value for null value for property"() { 9 | def jsonSlurper = new JsonSlurper() 10 | when: 11 | PropertiesToJsonConverter converter = new PropertiesToJsonConverter() 12 | String json = converter.convertPropertiesFromFileToJson("src/test/resources/primitiveTypes.properties") 13 | println(json) 14 | def jsonObject = jsonSlurper.parseText(json) 15 | then: 16 | jsonObject.complexObject.nullValue == null 17 | jsonObject.complexObject.empty == "" 18 | } 19 | 20 | def "json has null for null value of element in map"() { 21 | def jsonSlurper = new JsonSlurper() 22 | when: 23 | PropertiesToJsonConverter converter = new PropertiesToJsonConverter() 24 | Map map = [:] 25 | map.put("man.nullValue", null) 26 | map.put("man.nullToo", "null") 27 | map.put("man.empty", "") 28 | String json = converter.convertToJson(map) 29 | println(json) 30 | def jsonObject = jsonSlurper.parseText(json) 31 | then: 32 | jsonObject.man.nullValue == null 33 | jsonObject.man.nullToo == null 34 | jsonObject.man.empty == "" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/test/java/pl/jalokim/propertiestojson/util/JsonCheckerUtil.java: -------------------------------------------------------------------------------- 1 | package pl.jalokim.propertiestojson.util; 2 | 3 | import com.google.gson.JsonObject; 4 | import pl.jalokim.propertiestojson.resolvers.primitives.utils.JsonObjectHelper; 5 | 6 | public class JsonCheckerUtil { 7 | 8 | public static boolean leafOfPathIsNotPresent(String jsonPath, String jsonAsText) { 9 | JsonObject jsonObject = JsonObjectHelper.toJsonElement(jsonAsText).getAsJsonObject(); 10 | String[] jsonPaths = jsonPath.split("\\."); 11 | 12 | for (int i = 0; i < jsonPath.length(); i++) { 13 | if (i == jsonPaths.length - 1) { 14 | return !jsonObject.has(jsonPaths[i]); 15 | } 16 | jsonObject = jsonObject.getAsJsonObject(jsonPaths[i]); 17 | } 18 | throw new IllegalArgumentException("cannot find path: " + jsonPath + " in json" + jsonAsText); 19 | } 20 | 21 | public static boolean leafOfPathHasNullValue(String jsonPath, String jsonAsText) { 22 | JsonObject jsonObject = JsonObjectHelper.toJsonElement(jsonAsText).getAsJsonObject(); 23 | String[] jsonPaths = jsonPath.split("\\."); 24 | 25 | for (int i = 0; i < jsonPath.length(); i++) { 26 | if (i == jsonPaths.length - 1) { 27 | return jsonObject.get(jsonPaths[i]).isJsonNull(); 28 | } 29 | jsonObject = jsonObject.getAsJsonObject(jsonPaths[i]); 30 | } 31 | throw new IllegalArgumentException("cannot find path: " + jsonPath + " in json" + jsonAsText); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/pl/jalokim/propertiestojson/util/exception/ParsePropertiesException.java: -------------------------------------------------------------------------------- 1 | package pl.jalokim.propertiestojson.util.exception; 2 | 3 | import pl.jalokim.propertiestojson.resolvers.primitives.StringJsonTypeResolver; 4 | import pl.jalokim.propertiestojson.resolvers.primitives.object.StringToJsonTypeConverter; 5 | 6 | public class ParsePropertiesException extends RuntimeException { 7 | 8 | private static final long serialVersionUID = 1L; 9 | 10 | public static final String STRING_RESOLVER_AS_NOT_LAST = "Added some type resolver after " + StringJsonTypeResolver.class.getCanonicalName() 11 | + ". This type resolver always should be last when is in configuration of resolvers"; 12 | 13 | public static final String STRING_TO_JSON_RESOLVER_AS_NOT_LAST = "Added some type resolver after " + StringToJsonTypeConverter.class.getCanonicalName() 14 | + ". This type resolver always should be last when is in configuration of resolvers"; 15 | 16 | public static final String PROPERTY_KEY_NEEDS_TO_BE_STRING_TYPE = "Unsupported property key type: %s for key: %s, Property key needs to be a string type"; 17 | public static final String CANNOT_FIND_TYPE_RESOLVER_MSG = "Cannot find valid JSON type resolver for class: '%s'. %n" 18 | + "Please consider add sufficient resolver to your resolvers."; 19 | 20 | public static final String CANNOT_FIND_JSON_TYPE_OBJ = "Cannot find valid JSON type resolver for class: '%s'. %n" 21 | + " for property: %s, and object value: %s %n" 22 | + "Please consider add sufficient resolver to your resolvers."; 23 | 24 | public ParsePropertiesException(String message) { 25 | super(message); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/pl/jalokim/propertiestojson/PropertyArrayHelper.java: -------------------------------------------------------------------------------- 1 | package pl.jalokim.propertiestojson; 2 | 3 | import static pl.jalokim.propertiestojson.Constants.ARRAY_END_SIGN; 4 | import static pl.jalokim.propertiestojson.Constants.ARRAY_START_SIGN; 5 | import static pl.jalokim.propertiestojson.Constants.EMPTY_STRING; 6 | import static pl.jalokim.propertiestojson.Constants.SIMPLE_ARRAY_DELIMITER; 7 | import static pl.jalokim.propertiestojson.path.PathMetadata.INDEXES_PATTERN; 8 | 9 | import java.util.ArrayList; 10 | import java.util.List; 11 | import lombok.Getter; 12 | 13 | @Getter 14 | public class PropertyArrayHelper { 15 | 16 | private List dimensionalIndexes; 17 | private String arrayFieldName; 18 | 19 | public PropertyArrayHelper(String field) { 20 | arrayFieldName = getNameFromArray(field); 21 | dimensionalIndexes = getIndexesFromArrayField(field); 22 | } 23 | 24 | public static String getNameFromArray(String fieldName) { 25 | return fieldName.replaceFirst(INDEXES_PATTERN + "$", EMPTY_STRING); 26 | } 27 | 28 | public static List getIndexesFromArrayField(String fieldName) { 29 | String indexesAsText = fieldName.replace(getNameFromArray(fieldName), EMPTY_STRING); 30 | String[] indexesAsTextArray = indexesAsText 31 | .replace(ARRAY_START_SIGN, EMPTY_STRING) 32 | .replace(ARRAY_END_SIGN, SIMPLE_ARRAY_DELIMITER) 33 | .replaceAll("\\s", EMPTY_STRING) 34 | .split(SIMPLE_ARRAY_DELIMITER); 35 | List indexes = new ArrayList<>(); 36 | for (String indexAsText : indexesAsTextArray) { 37 | indexes.add(Integer.valueOf(indexAsText)); 38 | } 39 | return indexes; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/pl/jalokim/propertiestojson/resolvers/ObjectJsonTypeResolver.java: -------------------------------------------------------------------------------- 1 | package pl.jalokim.propertiestojson.resolvers; 2 | 3 | import pl.jalokim.propertiestojson.JsonObjectFieldsValidator; 4 | import pl.jalokim.propertiestojson.object.AbstractJsonType; 5 | import pl.jalokim.propertiestojson.object.ObjectJsonType; 6 | import pl.jalokim.propertiestojson.path.PathMetadata; 7 | 8 | public class ObjectJsonTypeResolver extends JsonTypeResolver { 9 | 10 | @Override 11 | public ObjectJsonType traverse(PathMetadata currentPathMetaData) { 12 | fetchJsonObjectOrCreate(currentPathMetaData); 13 | return currentObjectJsonType; 14 | } 15 | 16 | private void fetchJsonObjectOrCreate(PathMetadata currentPathMetaData) { 17 | if (currentObjectJsonType.containsField(currentPathMetaData.getFieldName())) { 18 | fetchJsonObjectWhenIsNotPrimitive(currentPathMetaData); 19 | } else { 20 | createNewJsonObjectAndAssignToCurrent(currentPathMetaData); 21 | } 22 | } 23 | 24 | private void createNewJsonObjectAndAssignToCurrent(PathMetadata currentPathMetaData) { 25 | ObjectJsonType nextObjectJsonType = new ObjectJsonType(); 26 | currentObjectJsonType.addField(currentPathMetaData.getFieldName(), nextObjectJsonType, currentPathMetaData); 27 | currentObjectJsonType = nextObjectJsonType; 28 | } 29 | 30 | private void fetchJsonObjectWhenIsNotPrimitive(PathMetadata currentPathMetaData) { 31 | AbstractJsonType jsonType = currentObjectJsonType.getField(currentPathMetaData.getFieldName()); 32 | JsonObjectFieldsValidator.checkEarlierWasJsonObject(propertyKey, currentPathMetaData, jsonType); 33 | currentObjectJsonType = (ObjectJsonType) jsonType; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/pl/jalokim/propertiestojson/resolvers/primitives/string/TextToNumberResolver.java: -------------------------------------------------------------------------------- 1 | package pl.jalokim.propertiestojson.resolvers.primitives.string; 2 | 3 | import java.math.BigDecimal; 4 | import java.math.BigInteger; 5 | import java.util.Optional; 6 | import pl.jalokim.propertiestojson.resolvers.PrimitiveJsonTypesResolver; 7 | 8 | public class TextToNumberResolver implements TextToConcreteObjectResolver { 9 | 10 | public static Number convertToNumber(String propertyValue) { 11 | Number number = convertToNumberFromText(propertyValue); 12 | if (number != null && number.toString().equals(propertyValue)) { 13 | return number; 14 | } 15 | return null; 16 | } 17 | 18 | @SuppressWarnings("PMD.EmptyCatchBlock") 19 | private static Number convertToNumberFromText(String propertyValue) { 20 | 21 | try { 22 | return getIntegerNumber(propertyValue); 23 | } catch (NumberFormatException exc) { 24 | // NOTHING TO DO 25 | } 26 | try { 27 | return getDoubleNumber(propertyValue); 28 | } catch (NumberFormatException exc) { 29 | // NOTHING TO DO 30 | } 31 | return null; 32 | } 33 | 34 | private static BigInteger getIntegerNumber(String toParse) { 35 | return new BigInteger(toParse); 36 | } 37 | 38 | private static BigDecimal getDoubleNumber(String toParse) { 39 | return new BigDecimal(toParse); 40 | } 41 | 42 | @Override 43 | public Optional returnObjectWhenCanBeResolved(PrimitiveJsonTypesResolver primitiveJsonTypesResolver, String propertyValue, String propertyKey) { 44 | return Optional.ofNullable(convertToNumber(propertyValue)); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/test/java/pl/jalokim/propertiestojson/resolvers/primitives/custom/LocalDateToJsonTypeConverterTest.java: -------------------------------------------------------------------------------- 1 | package pl.jalokim.propertiestojson.resolvers.primitives.custom; 2 | 3 | import static org.assertj.core.api.Assertions.assertThat; 4 | 5 | import java.time.LocalDate; 6 | import org.junit.Test; 7 | import pl.jalokim.propertiestojson.object.AbstractJsonType; 8 | import pl.jalokim.propertiestojson.object.NumberJsonType; 9 | import pl.jalokim.propertiestojson.object.ObjectJsonType; 10 | 11 | public class LocalDateToJsonTypeConverterTest { 12 | 13 | @Test 14 | public void convertLocalDateToUtcTimestamp() { 15 | // given 16 | LocalDate localDate = LocalDate.of(2019, 8, 4); 17 | LocalDateToJsonTypeConverter resolver = new LocalDateToJsonTypeConverter(true); 18 | // when 19 | AbstractJsonType jsonObject = resolver.convertToJsonTypeOrEmpty(null, localDate, "some.field").get(); 20 | // then 21 | assertThat(jsonObject).isNotNull(); 22 | NumberJsonType numberJsonType = (NumberJsonType) jsonObject; 23 | assertThat(numberJsonType.toString()).isEqualTo("1564876800"); 24 | } 25 | 26 | @Test 27 | public void convertLocalDateToJsonObject() { 28 | // given 29 | LocalDate localDate = LocalDate.of(2019, 8, 4); 30 | LocalDateToJsonTypeConverter resolver = new LocalDateToJsonTypeConverter(false); 31 | // when 32 | AbstractJsonType jsonObject = resolver.convertToJsonTypeOrEmpty(null, localDate, "some.field").get(); 33 | // then 34 | assertThat(jsonObject).isNotNull(); 35 | ObjectJsonType numberJsonType = (ObjectJsonType) jsonObject; 36 | assertThat(numberJsonType.toString()).isEqualTo("{\"year\":2019,\"month\":8,\"day\":4}"); 37 | } 38 | } -------------------------------------------------------------------------------- /src/test/java/pl/jalokim/propertiestojson/resolvers/primitives/PrimitiveJsonTypeResolverTest.java: -------------------------------------------------------------------------------- 1 | package pl.jalokim.propertiestojson.resolvers.primitives; 2 | 3 | 4 | import java.util.Optional; 5 | import org.junit.Rule; 6 | import org.junit.Test; 7 | import org.junit.rules.ExpectedException; 8 | import pl.jalokim.propertiestojson.object.AbstractJsonType; 9 | import pl.jalokim.propertiestojson.resolvers.PrimitiveJsonTypesResolver; 10 | import pl.jalokim.propertiestojson.util.exception.ParsePropertiesException; 11 | 12 | public class PrimitiveJsonTypeResolverTest { 13 | 14 | @Rule 15 | public ExpectedException expectedEx = ExpectedException.none(); 16 | 17 | @Test 18 | public void cannotResolveGenericTypeOfClass() { 19 | // then 20 | expectedEx.expect(ParsePropertiesException.class); 21 | expectedEx.expectMessage("Cannot find generic type for resolver: " + SomePrimitiveJsonTypeResolver.class + " You can resolve it by one of below:" 22 | + "\n 1. override method resolveTypeOfResolver() for provide explicit class type " + 23 | "\n 2. add generic type during extension of PrimitiveJsonTypeResolver " 24 | + "'class " + SomePrimitiveJsonTypeResolver.class.getSimpleName() + " extends PrimitiveJsonTypeResolver'"); 25 | // when 26 | new SomePrimitiveJsonTypeResolver(); 27 | } 28 | 29 | private static class SomePrimitiveJsonTypeResolver extends PrimitiveJsonTypeResolver { 30 | 31 | @Override 32 | protected Optional returnConcreteValueWhenCanBeResolved(PrimitiveJsonTypesResolver primitiveJsonTypesResolver, String propertyValue, 33 | String propertyKey) { 34 | return Optional.empty(); 35 | } 36 | 37 | @Override 38 | public AbstractJsonType returnConcreteJsonType(PrimitiveJsonTypesResolver primitiveJsonTypesResolver, Object propertyValue, String propertyKey) { 39 | return null; 40 | } 41 | } 42 | 43 | } -------------------------------------------------------------------------------- /src/test/java/pl/jalokim/propertiestojson/resolvers/primitives/custom/TextToLocalDateResolverTest.java: -------------------------------------------------------------------------------- 1 | package pl.jalokim.propertiestojson.resolvers.primitives.custom; 2 | 3 | import static java.time.Month.AUGUST; 4 | import static org.assertj.core.api.Assertions.assertThat; 5 | 6 | import java.time.LocalDate; 7 | import java.util.Optional; 8 | import org.junit.Test; 9 | 10 | public class TextToLocalDateResolverTest { 11 | 12 | @Test 13 | public void convertToLocalDateFromText() { 14 | // given 15 | TextToLocalDateResolver resolver = new TextToLocalDateResolver(); 16 | // when 17 | Optional localDateOptional = resolver.returnObjectWhenCanBeResolved(null, "04-08-2019", "some.field"); 18 | // then 19 | assertThat(localDateOptional.isPresent()).isTrue(); 20 | LocalDate localDate = localDateOptional.get(); 21 | assertThat(localDate.getYear()).isEqualTo(2019); 22 | assertThat(localDate.getMonth()).isEqualTo(AUGUST); 23 | assertThat(localDate.getDayOfMonth()).isEqualTo(4); 24 | } 25 | 26 | @Test 27 | public void convertToLocalDateFromTextForAnotherFormat() { 28 | // given 29 | TextToLocalDateResolver resolver = new TextToLocalDateResolver("dd/MM/yyyy"); 30 | // when 31 | Optional localDateOptional = resolver.returnObjectWhenCanBeResolved(null, "04/08/2019", "some.field"); 32 | // then 33 | assertThat(localDateOptional.isPresent()).isTrue(); 34 | LocalDate localDate = localDateOptional.get(); 35 | assertThat(localDate.getYear()).isEqualTo(2019); 36 | assertThat(localDate.getMonth()).isEqualTo(AUGUST); 37 | assertThat(localDate.getDayOfMonth()).isEqualTo(4); 38 | } 39 | 40 | @Test 41 | public void notConvertToLocalDateFromTextForAnotherFormat() { 42 | // given 43 | TextToLocalDateResolver resolver = new TextToLocalDateResolver("dd/MM/yyyy"); 44 | // when 45 | Optional localDate = resolver.returnObjectWhenCanBeResolved(null, "m/08/2019", "some.field"); 46 | // then 47 | assertThat(localDate.isPresent()).isFalse(); 48 | } 49 | 50 | } -------------------------------------------------------------------------------- /src/main/java/pl/jalokim/propertiestojson/helper/PropertiesWithInsertOrder.java: -------------------------------------------------------------------------------- 1 | package pl.jalokim.propertiestojson.helper; 2 | 3 | import java.util.LinkedHashSet; 4 | import java.util.Objects; 5 | import java.util.Properties; 6 | import java.util.Set; 7 | import java.util.function.BiConsumer; 8 | 9 | /** 10 | * Implementation for Properties which stores keys in the same order like were put. It supports order only in {@link #forEach(BiConsumer)} and {@link #keySet()} 11 | * methods. 12 | */ 13 | public class PropertiesWithInsertOrder extends Properties { 14 | 15 | private static final long serialVersionUID = 1L; 16 | 17 | private final Set orderedKeys = new LinkedHashSet<>(); 18 | 19 | @Override 20 | public synchronized Object put(Object key, Object value) { 21 | orderedKeys.add(key); 22 | return super.put(key, value); 23 | } 24 | 25 | @Override 26 | public synchronized boolean remove(Object key, Object value) { 27 | boolean removed = super.remove(key, value); 28 | if (removed) { 29 | orderedKeys.remove(key); 30 | } 31 | return removed; 32 | } 33 | 34 | @Override 35 | public synchronized Object remove(Object key) { 36 | orderedKeys.remove(key); 37 | return super.remove(key); 38 | } 39 | 40 | @Override 41 | public synchronized void forEach(BiConsumer action) { 42 | keySet().forEach(key -> action.accept(key, get(key))); 43 | } 44 | 45 | @Override 46 | public synchronized Set keySet() { 47 | return orderedKeys; 48 | } 49 | 50 | @Override 51 | public boolean equals(Object o) { 52 | if (this == o) { 53 | return true; 54 | } 55 | if (!(o instanceof PropertiesWithInsertOrder)) { 56 | return false; 57 | } 58 | if (!super.equals(o)) { 59 | return false; 60 | } 61 | PropertiesWithInsertOrder that = (PropertiesWithInsertOrder) o; 62 | return Objects.equals(orderedKeys, that.orderedKeys); 63 | } 64 | 65 | @Override 66 | public int hashCode() { 67 | return Objects.hash(super.hashCode(), orderedKeys); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/test/java/pl/jalokim/propertiestojson/helper/PropertiesWithInsertOrderTest.java: -------------------------------------------------------------------------------- 1 | package pl.jalokim.propertiestojson.helper; 2 | 3 | 4 | import static org.assertj.core.api.Assertions.assertThat; 5 | 6 | import java.util.Arrays; 7 | import java.util.Iterator; 8 | import java.util.List; 9 | import java.util.Properties; 10 | import java.util.concurrent.atomic.AtomicInteger; 11 | import org.junit.Test; 12 | 13 | public class PropertiesWithInsertOrderTest { 14 | 15 | @Test 16 | public void verifyKeySetThatOrderIsLikeOrderOfInsertions() { 17 | // when 18 | Properties properties = new PropertiesWithInsertOrder(); 19 | properties.put("field.sdfgs", 1); 20 | properties.put("fsdfgield.g", 2); 21 | properties.put("sdfgs.field3", 3); 22 | properties.put("nextField.sdfg", 4); 23 | properties.put("dfgsf.field5", 5); 24 | // then 25 | AtomicInteger currentValue = new AtomicInteger(0); 26 | properties.keySet().forEach(key -> { 27 | assertThat(properties.get(key)).isEqualTo(currentValue.incrementAndGet()); 28 | }); 29 | assertThat(currentValue.get()).isEqualTo(5); 30 | } 31 | 32 | @Test 33 | public void removePropertiesImpactToKeySetAndForeachAndKeys() { 34 | // given 35 | Properties properties = new PropertiesWithInsertOrder(); 36 | properties.put("field.sdfgs", 1); 37 | properties.put("fsdfgield.g", 2); 38 | properties.put("sdfgs.field3", 3); 39 | properties.put("nextField.sdfg", 4); 40 | properties.put("dfgsf.field5", 5); 41 | // when 42 | properties.remove("field.sdfgs"); 43 | properties.remove("sdfgs.field3", 4); 44 | properties.remove("dfgsf.field5", 5); 45 | // then 46 | List expectedValues = Arrays.asList(2, 3, 4); 47 | Iterator iterator1 = expectedValues.iterator(); 48 | // key set 49 | properties.keySet().forEach(key -> 50 | assertThat(properties.get(key)).isEqualTo(iterator1.next()) 51 | ); 52 | Iterator iterator2 = expectedValues.iterator(); 53 | properties.forEach((key, value) -> 54 | assertThat(properties.get(key)).isEqualTo(iterator2.next()) 55 | ); 56 | } 57 | } -------------------------------------------------------------------------------- /src/main/java/pl/jalokim/propertiestojson/resolvers/primitives/object/ElementsToJsonTypeConverter.java: -------------------------------------------------------------------------------- 1 | package pl.jalokim.propertiestojson.resolvers.primitives.object; 2 | 3 | import static java.util.Collections.singletonList; 4 | 5 | import java.util.ArrayList; 6 | import java.util.Arrays; 7 | import java.util.Collection; 8 | import java.util.List; 9 | import java.util.Optional; 10 | import pl.jalokim.propertiestojson.object.AbstractJsonType; 11 | import pl.jalokim.propertiestojson.object.ArrayJsonType; 12 | import pl.jalokim.propertiestojson.resolvers.PrimitiveJsonTypesResolver; 13 | 14 | public class ElementsToJsonTypeConverter extends AbstractObjectToJsonTypeConverter> { 15 | 16 | @Override 17 | public Optional convertToJsonTypeOrEmpty(PrimitiveJsonTypesResolver primitiveJsonTypesResolver, 18 | Collection elements, 19 | String propertyKey) { 20 | return Optional.of(new ArrayJsonType(primitiveJsonTypesResolver, elements, null, propertyKey)); 21 | } 22 | 23 | @Override 24 | public Optional returnOptionalJsonType(PrimitiveJsonTypesResolver primitiveJsonTypesResolver, Object propertyValue, String propertyKey) { 25 | if (hasElements(propertyValue.getClass())) { 26 | Collection collection; 27 | if (propertyValue.getClass().isArray()) { 28 | Object[] rawArray = (Object[]) propertyValue; 29 | collection = new ArrayList<>(Arrays.asList(rawArray)); 30 | } else { 31 | collection = (Collection) propertyValue; 32 | } 33 | return convertToJsonTypeOrEmpty(primitiveJsonTypesResolver, collection, propertyKey); 34 | } 35 | return convertToJsonTypeOrEmpty(primitiveJsonTypesResolver, singletonList(propertyValue), propertyKey); 36 | } 37 | 38 | public boolean hasElements(Class classToTest) { 39 | return classesWhichCanResolved.isAssignableFrom(classToTest) || classToTest.isArray(); 40 | } 41 | 42 | @Override 43 | public Class resolveTypeOfResolver() { 44 | return Collection.class; 45 | } 46 | 47 | @Override 48 | public List> getClassesWhichCanResolve() { 49 | return Arrays.asList(Collection.class, Object[].class); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/pl/jalokim/propertiestojson/resolvers/primitives/ObjectFromTextJsonTypeResolver.java: -------------------------------------------------------------------------------- 1 | package pl.jalokim.propertiestojson.resolvers.primitives; 2 | 3 | import pl.jalokim.propertiestojson.object.AbstractJsonType; 4 | import pl.jalokim.propertiestojson.resolvers.PrimitiveJsonTypesResolver; 5 | import pl.jalokim.propertiestojson.resolvers.primitives.delegator.PrimitiveJsonTypeDelegatorResolver; 6 | import pl.jalokim.propertiestojson.resolvers.primitives.object.SuperObjectToJsonTypeConverter; 7 | import pl.jalokim.propertiestojson.resolvers.primitives.string.TextToObjectResolver; 8 | import pl.jalokim.propertiestojson.util.PropertiesToJsonConverter; 9 | 10 | 11 | /** 12 | * When given text contains parsable json value, json object or json array then try build instance of ObjectJsonType or ArrayJsonType It will invoke {@link 13 | * #returnConcreteJsonType(PrimitiveJsonTypesResolver, Object, String)} after conversion from string (raw property value to some object) This Resolver will 14 | * convert number in json as number, text as text, boolean as boolean... It uses independent, own list of json type resolvers. The setup of resolvers in {@link 15 | * PropertiesToJsonConverter#PropertiesToJsonConverter(PrimitiveJsonTypeResolver... primitiveResolvers)} will not have impact of those list. 16 | */ 17 | @Deprecated 18 | public class ObjectFromTextJsonTypeResolver extends PrimitiveJsonTypeDelegatorResolver { 19 | 20 | public ObjectFromTextJsonTypeResolver() { 21 | super(new TextToObjectResolver(), new SuperObjectToJsonTypeConverter()); 22 | } 23 | 24 | /** 25 | * It convert to implementation of AbstractJsonType through use of json for conversion from java object to raw json, then raw json convert to 26 | * com.google.gson.JsonElement, and this JsonElement to instance of AbstractJsonType (json object, array json, or simple text json) 27 | * 28 | * @param propertyValue java bean to convert to instance of AbstractJsonType. 29 | * @param propertyKey currently processed propertyKey from properties. 30 | * @return instance of AbstractJsonType 31 | */ 32 | public static AbstractJsonType convertFromObjectToJson(Object propertyValue, String propertyKey) { 33 | return SuperObjectToJsonTypeConverter.convertFromObjectToJson(propertyValue, propertyKey); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/test/java/pl/jalokim/propertiestojson/resolvers/primitives/example/LocalDateTimeResolver.java: -------------------------------------------------------------------------------- 1 | package pl.jalokim.propertiestojson.resolvers.primitives.example; 2 | 3 | import java.time.LocalDate; 4 | import java.time.ZoneOffset; 5 | import java.time.format.DateTimeFormatter; 6 | import java.util.Optional; 7 | import pl.jalokim.propertiestojson.object.AbstractJsonType; 8 | import pl.jalokim.propertiestojson.object.NumberJsonType; 9 | import pl.jalokim.propertiestojson.resolvers.PrimitiveJsonTypesResolver; 10 | import pl.jalokim.propertiestojson.resolvers.primitives.ObjectFromTextJsonTypeResolver; 11 | import pl.jalokim.propertiestojson.resolvers.primitives.PrimitiveJsonTypeResolver; 12 | 13 | /* 14 | Class only for check backward compatibility with extend of PrimitiveJsonTypeResolver... 15 | */ 16 | public class LocalDateTimeResolver extends PrimitiveJsonTypeResolver { 17 | 18 | private static final String DATE_FORMAT = "dd-MM-yyyy"; 19 | private final DateTimeFormatter formatter; 20 | private final boolean asTimestampInUTC; 21 | 22 | LocalDateTimeResolver() { 23 | this(DATE_FORMAT, false); 24 | } 25 | 26 | LocalDateTimeResolver(boolean asTimestampInUTC) { 27 | this(DATE_FORMAT, asTimestampInUTC); 28 | } 29 | 30 | LocalDateTimeResolver(String formatOfDate) { 31 | this(formatOfDate, false); 32 | } 33 | 34 | LocalDateTimeResolver(String formatOfDate, boolean asTimestampInUTC) { 35 | formatter = DateTimeFormatter.ofPattern(formatOfDate); 36 | this.asTimestampInUTC = asTimestampInUTC; 37 | } 38 | 39 | @Override 40 | protected Optional returnConcreteValueWhenCanBeResolved(PrimitiveJsonTypesResolver primitiveJsonTypesResolver, 41 | String propertyValue, 42 | String propertyKey) { 43 | try { 44 | return Optional.ofNullable(LocalDate.parse(propertyValue, formatter)); // if parse then will return LocalDate 45 | } catch (Exception ex) { 46 | return Optional.empty(); // if not 47 | } 48 | } 49 | 50 | @Override 51 | public AbstractJsonType returnConcreteJsonType(PrimitiveJsonTypesResolver primitiveJsonTypesResolver, 52 | LocalDate convertedValue, 53 | String propertyKey) { 54 | if (asTimestampInUTC) { 55 | return new NumberJsonType(convertedValue.atStartOfDay(ZoneOffset.UTC).toEpochSecond()); 56 | } 57 | return ObjectFromTextJsonTypeResolver.convertFromObjectToJson(convertedValue, propertyKey); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/main/java/pl/jalokim/propertiestojson/resolvers/primitives/custom/TextToLocalDateResolver.java: -------------------------------------------------------------------------------- 1 | package pl.jalokim.propertiestojson.resolvers.primitives.custom; 2 | 3 | import java.time.LocalDate; 4 | import java.time.format.DateTimeFormatter; 5 | import java.util.Optional; 6 | import pl.jalokim.propertiestojson.resolvers.PrimitiveJsonTypesResolver; 7 | import pl.jalokim.propertiestojson.resolvers.primitives.string.TextToConcreteObjectResolver; 8 | 9 | /** 10 | * results of this resolver you can see in those test classes: 11 | * 12 | * @see LocalDateTimeResolverTest 13 | * @see LocalDateTimeResolverTest 14 | */ 15 | public class TextToLocalDateResolver implements TextToConcreteObjectResolver { 16 | 17 | private static final String DATE_FORMAT = "dd-MM-yyyy"; 18 | private final DateTimeFormatter formatter; 19 | 20 | public TextToLocalDateResolver() { 21 | this(DATE_FORMAT); 22 | } 23 | 24 | public TextToLocalDateResolver(String formatOfDate) { 25 | formatter = DateTimeFormatter.ofPattern(formatOfDate); 26 | } 27 | 28 | /** 29 | * This method will be called in first conversion phase if your condition is met then return concrete value of Object. if it doesn't meet its condition then 30 | * return Optional.empty() for allow go to others type resolver in order. This will be called only for read properties from Map<String,String>, File 31 | * with properties, InputStream with properties 32 | * 33 | * @param primitiveJsonTypesResolver primitiveJsonTypesResolver 34 | * @param propertyValue currently processing property value 35 | * @param propertyKey currently processing property key 36 | * @return optional value 37 | */ 38 | 39 | @Override 40 | public Optional returnObjectWhenCanBeResolved(PrimitiveJsonTypesResolver primitiveJsonTypesResolver, 41 | String propertyValue, 42 | String propertyKey) { 43 | try { 44 | return Optional.ofNullable(LocalDate.parse(propertyValue, formatter)); // if parse then will return LocalDate 45 | } catch (Exception ex) { 46 | return Optional.empty(); // if not, then allow another resolvers to crate java object from String 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | 6 | pl.jalokim.parentpom 7 | java-parent-pom 8 | 1.0.2 9 | 10 | 11 | pl.jalokim.propertiestojson 12 | java-properties-to-json 13 | ${main-version}.${minor-version} 14 | jar 15 | 16 | 17 | 5.3 18 | 1-SNAPSHOT 19 | 20 | 21 | 1.8 22 | Java properties to Json 23 | Library used to convert from java properties to json 24 | java-utils 25 | java-properties-to-json 26 | 2cWt0w37Yh0llv0dv4uZOMNBq5LIh9qZA 27 | 3.1.0 28 | 29 | 30 | 31 | 32 | 33 | com.google.code.gson 34 | gson 35 | 2.8.9 36 | 37 | 38 | com.google.guava 39 | guava 40 | 31.0.1-jre 41 | 42 | 43 | org.projectlombok 44 | lombok 45 | 1.18.8 46 | provided 47 | 48 | 49 | pl.jalokim.utils 50 | java-utils 51 | ${java-utils.version} 52 | 53 | 54 | 55 | 56 | pl.jalokim.utils 57 | test-utils 58 | ${java-utils.version} 59 | test 60 | 61 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /src/test/groovy/pl/jalokim/propertiestojson/path/PathMetadataBuilderTest.groovy: -------------------------------------------------------------------------------- 1 | package pl.jalokim.propertiestojson.path 2 | 3 | import java.util.concurrent.atomic.AtomicReference 4 | import org.assertj.core.api.Assertions 5 | import spock.lang.Specification 6 | 7 | class PathMetadataBuilderTest extends Specification { 8 | 9 | def "return expected list of property parts"() { 10 | given: 11 | String propertyKey = "field.nextfield.array[12][12].anotherField[external.key.leaf].anotherField[[external.key.leaf][some.strange.things]].fieldLeaf" 12 | when: 13 | def parts = PathMetadataBuilder.getPropertyParts(propertyKey) 14 | then: 15 | parts[0] == "field" 16 | parts[1] == "nextfield" 17 | parts[2] == "array[12][12]" 18 | parts[3] == "anotherField[external.key.leaf]" 19 | parts[4] == "anotherField[[external.key.leaf][some.strange.things]]" 20 | parts[5] == "fieldLeaf" 21 | } 22 | 23 | def "create Expected path metadata"() { 24 | given: 25 | String propertyKey = "field.nextfield.array[12][12].fieldLeaf" 26 | when: 27 | PathMetadata root = PathMetadataBuilder.createRootPathMetaData(propertyKey) 28 | AtomicReference reference = new AtomicReference(root) 29 | then: 30 | hasExpectedValues(reference, true, false, "field", "field", propertyKey) 31 | hasExpectedValues(reference, false, false, "nextfield", "field.nextfield", propertyKey) 32 | hasExpectedValues(reference, false, false, "array[12][12]", "field.nextfield.array[12][12]", propertyKey) 33 | hasExpectedValues(reference, false, true, "fieldLeaf", "field.nextfield.array[12][12].fieldLeaf", propertyKey) 34 | 35 | root.getLeaf().getOriginalPropertyKey() == propertyKey 36 | root.getLeaf().getFieldName() == "fieldLeaf" 37 | root.getLeaf().getRoot().getFieldName() == "field" 38 | } 39 | 40 | private static boolean hasExpectedValues(AtomicReference currentReference, boolean isRoot, boolean isLeaf, 41 | String fieldName, String currentFullPath, String originalPropertyKey) { 42 | PathMetadata pathMetadata = currentReference.get() 43 | currentReference.set(pathMetadata.getChild()) 44 | 45 | Assertions.assertThat(pathMetadata.isRoot()).isEqualTo(isRoot) 46 | Assertions.assertThat(pathMetadata.isLeaf()).isEqualTo(isLeaf) 47 | Assertions.assertThat(pathMetadata.getOriginalFieldName()).isEqualTo(fieldName) 48 | Assertions.assertThat(pathMetadata.getCurrentFullPath()).isEqualTo(currentFullPath) 49 | Assertions.assertThat(pathMetadata.getOriginalPropertyKey()).isEqualTo(originalPropertyKey) 50 | 51 | return true 52 | } 53 | 54 | } 55 | 56 | -------------------------------------------------------------------------------- /src/test/groovy/pl/jalokim/propertiestojson/JsonObjectFieldsValidatorTest.groovy: -------------------------------------------------------------------------------- 1 | package pl.jalokim.propertiestojson 2 | 3 | import static pl.jalokim.propertiestojson.JsonObjectFieldsValidator.isArrayJson 4 | import static pl.jalokim.propertiestojson.JsonObjectFieldsValidator.isObjectJson 5 | import static pl.jalokim.propertiestojson.JsonObjectFieldsValidator.isPrimitiveValue 6 | 7 | import pl.jalokim.propertiestojson.object.AbstractJsonType 8 | import pl.jalokim.propertiestojson.object.ArrayJsonType 9 | import pl.jalokim.propertiestojson.object.JsonNullReferenceType 10 | import pl.jalokim.propertiestojson.object.NumberJsonType 11 | import pl.jalokim.propertiestojson.object.ObjectJsonType 12 | import pl.jalokim.propertiestojson.object.StringJsonType 13 | import spock.lang.Specification 14 | 15 | class JsonObjectFieldsValidatorTest extends Specification { 16 | 17 | def "is type of json object"(AbstractJsonType someObject, boolean expectedResult) { 18 | when: 19 | boolean result = isObjectJson(someObject) 20 | then: 21 | result == expectedResult 22 | where: 23 | someObject | expectedResult 24 | new ObjectJsonType() | true 25 | new StringJsonType("test") | false 26 | new ArrayJsonType() | false 27 | new OwnObjectJsonType() | true 28 | new JsonNullReferenceType() | false 29 | } 30 | 31 | def "is type of json array"(AbstractJsonType someObject, boolean expectedResult) { 32 | when: 33 | boolean result = isArrayJson(someObject) 34 | then: 35 | result == expectedResult 36 | where: 37 | someObject | expectedResult 38 | new ObjectJsonType() | false 39 | new StringJsonType("test") | false 40 | new ArrayJsonType() | true 41 | new OwnObjectJsonType() | false 42 | new OwnArrayJsonType() | true 43 | new JsonNullReferenceType() | false 44 | } 45 | 46 | def "is type of primitive value"(AbstractJsonType someObject, boolean expectedResult) { 47 | when: 48 | boolean result = isPrimitiveValue(someObject) 49 | then: 50 | result == expectedResult 51 | where: 52 | someObject | expectedResult 53 | new ObjectJsonType() | false 54 | new StringJsonType("test") | true 55 | new NumberJsonType(12) | true 56 | new ArrayJsonType() | false 57 | new OwnObjectJsonType() | false 58 | new OwnArrayJsonType() | false 59 | new JsonNullReferenceType() | true 60 | } 61 | 62 | private static class OwnObjectJsonType extends ObjectJsonType { 63 | 64 | } 65 | 66 | private static class OwnArrayJsonType extends ArrayJsonType { 67 | 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/main/java/pl/jalokim/propertiestojson/resolvers/primitives/delegator/PrimitiveJsonTypeDelegatorResolver.java: -------------------------------------------------------------------------------- 1 | package pl.jalokim.propertiestojson.resolvers.primitives.delegator; 2 | 3 | import static pl.jalokim.utils.reflection.InvokableReflectionUtils.setValueForField; 4 | 5 | import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; 6 | import java.util.List; 7 | import java.util.Optional; 8 | import pl.jalokim.propertiestojson.object.AbstractJsonType; 9 | import pl.jalokim.propertiestojson.resolvers.PrimitiveJsonTypesResolver; 10 | import pl.jalokim.propertiestojson.resolvers.primitives.PrimitiveJsonTypeResolver; 11 | import pl.jalokim.propertiestojson.resolvers.primitives.object.AbstractObjectToJsonTypeConverter; 12 | import pl.jalokim.propertiestojson.resolvers.primitives.string.TextToConcreteObjectResolver; 13 | 14 | @SuppressWarnings("unchecked") 15 | @SuppressFBWarnings("UR_UNINIT_READ_CALLED_FROM_SUPER_CONSTRUCTOR") 16 | public class PrimitiveJsonTypeDelegatorResolver extends PrimitiveJsonTypeResolver { 17 | 18 | private final TextToConcreteObjectResolver toObjectResolver; 19 | private final AbstractObjectToJsonTypeConverter toJsonResolver; 20 | 21 | @SuppressWarnings("PMD.ConstructorCallsOverridableMethod") 22 | public PrimitiveJsonTypeDelegatorResolver(TextToConcreteObjectResolver toObjectResolver, 23 | AbstractObjectToJsonTypeConverter toJsonResolver) { 24 | this.toObjectResolver = toObjectResolver; 25 | this.toJsonResolver = toJsonResolver; 26 | setValueForField(this, "typeWhichCanBeResolved", resolveTypeOfResolver()); 27 | } 28 | 29 | @Override 30 | public Class resolveTypeOfResolver() { 31 | if (toJsonResolver != null) { 32 | return toJsonResolver.resolveTypeOfResolver(); 33 | } 34 | return null; 35 | } 36 | 37 | @Override 38 | public AbstractJsonType returnConcreteJsonType(PrimitiveJsonTypesResolver primitiveJsonTypesResolver, 39 | T convertedValue, 40 | String propertyKey) { 41 | Optional optional = toJsonResolver.convertToJsonTypeOrEmpty(primitiveJsonTypesResolver, 42 | convertedValue, 43 | propertyKey); 44 | return optional.get(); 45 | } 46 | 47 | @Override 48 | protected Optional returnConcreteValueWhenCanBeResolved(PrimitiveJsonTypesResolver primitiveJsonTypesResolver, 49 | String propertyValue, 50 | String propertyKey) { 51 | Optional optionalObject = toObjectResolver.returnObjectWhenCanBeResolved(primitiveJsonTypesResolver, 52 | propertyValue, propertyKey); 53 | return optionalObject.map(o -> (T) o); 54 | } 55 | 56 | @Override 57 | public List> getClassesWhichCanResolve() { 58 | return toJsonResolver.getClassesWhichCanResolve(); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/main/java/pl/jalokim/propertiestojson/resolvers/primitives/string/TextToElementsResolver.java: -------------------------------------------------------------------------------- 1 | package pl.jalokim.propertiestojson.resolvers.primitives.string; 2 | 3 | import static java.lang.String.join; 4 | import static pl.jalokim.propertiestojson.Constants.EMPTY_STRING; 5 | import static pl.jalokim.propertiestojson.Constants.SIMPLE_ARRAY_DELIMITER; 6 | import static pl.jalokim.propertiestojson.resolvers.primitives.utils.JsonObjectHelper.hasJsonArraySignature; 7 | import static pl.jalokim.propertiestojson.resolvers.primitives.utils.JsonObjectHelper.isValidJsonObjectOrArray; 8 | 9 | import java.util.ArrayList; 10 | import java.util.List; 11 | import java.util.Optional; 12 | import pl.jalokim.propertiestojson.resolvers.PrimitiveJsonTypesResolver; 13 | 14 | public class TextToElementsResolver implements TextToConcreteObjectResolver> { 15 | 16 | private final String arrayElementSeparator; 17 | private final boolean resolveTypeOfEachElement; 18 | 19 | public TextToElementsResolver() { 20 | this(true); 21 | } 22 | 23 | public TextToElementsResolver(boolean resolveTypeOfEachElement) { 24 | this(resolveTypeOfEachElement, SIMPLE_ARRAY_DELIMITER); 25 | } 26 | 27 | public TextToElementsResolver(boolean resolveTypeOfEachElement, String arrayElementSeparator) { 28 | this.resolveTypeOfEachElement = resolveTypeOfEachElement; 29 | this.arrayElementSeparator = arrayElementSeparator; 30 | } 31 | 32 | @Override 33 | @SuppressWarnings("PMD.AvoidReassigningParameters") 34 | public Optional> returnObjectWhenCanBeResolved(PrimitiveJsonTypesResolver primitiveJsonTypesResolver, String propertyValue, String propertyKey) { 35 | if (isSimpleArray(propertyValue) && !isValidJsonObjectOrArray(propertyValue)) { 36 | 37 | if (hasJsonArraySignature(propertyValue)) { 38 | propertyValue = propertyValue 39 | .replaceAll("]\\s*$", EMPTY_STRING) 40 | .replaceAll("^\\s*\\[\\s*", EMPTY_STRING); 41 | String[] elements = propertyValue.split(arrayElementSeparator); 42 | List clearedElements = new ArrayList<>(); 43 | for (String element : elements) { 44 | clearedElements.add(element.trim()); 45 | } 46 | propertyValue = join(arrayElementSeparator, clearedElements); 47 | } 48 | 49 | List elements = new ArrayList<>(); 50 | for (String element : propertyValue.split(arrayElementSeparator)) { 51 | if (resolveTypeOfEachElement) { 52 | elements.add(primitiveJsonTypesResolver.getResolvedObject(element, propertyKey)); 53 | } else { 54 | elements.add(element); 55 | } 56 | } 57 | return Optional.of(elements); 58 | } 59 | return Optional.empty(); 60 | } 61 | 62 | private boolean isSimpleArray(String propertyValue) { 63 | return propertyValue.contains(arrayElementSeparator) || hasJsonArraySignature(propertyValue); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/test/java/pl/jalokim/propertiestojson/resolvers/primitives/custom/LocalDateConvertersTest.java: -------------------------------------------------------------------------------- 1 | package pl.jalokim.propertiestojson.resolvers.primitives.custom; 2 | 3 | import static org.assertj.core.api.Assertions.assertThat; 4 | 5 | import com.google.gson.JsonElement; 6 | import com.google.gson.JsonObject; 7 | import com.google.gson.JsonParser; 8 | import java.util.HashMap; 9 | import java.util.Map; 10 | import org.junit.Test; 11 | import pl.jalokim.propertiestojson.util.PropertiesToJsonConverter; 12 | import pl.jalokim.propertiestojson.util.PropertiesToJsonConverterBuilder; 13 | 14 | public class LocalDateConvertersTest { 15 | 16 | private static final JsonParser jp = new JsonParser(); 17 | 18 | @Test 19 | public void convertFromTextDateThroughConverterToObjectJson() { 20 | // given 21 | TextToLocalDateResolver textToLocalDateResolver = new TextToLocalDateResolver(); 22 | LocalDateToJsonTypeConverter localDateToJsonTypeConverter = new LocalDateToJsonTypeConverter(); 23 | PropertiesToJsonConverter converter = PropertiesToJsonConverterBuilder.builder() 24 | .onlyCustomTextToObjectResolvers(textToLocalDateResolver) 25 | .onlyCustomObjectToJsonTypeConverters(localDateToJsonTypeConverter) 26 | .build(); 27 | Map properties = new HashMap<>(); 28 | properties.put("object.localDateField", "04-08-2019"); 29 | // when 30 | String json = converter.convertToJson(properties); 31 | // then 32 | System.out.println(json); 33 | JsonElement jsonElement = jp.parse(json); 34 | JsonObject asJsonObject = jsonElement.getAsJsonObject().getAsJsonObject("object"); 35 | JsonObject localDateJson = asJsonObject.getAsJsonObject("localDateField"); 36 | assertThat(localDateJson.get("year").getAsInt()).isEqualTo(2019); 37 | assertThat(localDateJson.get("month").getAsInt()).isEqualTo(8); 38 | assertThat(localDateJson.get("day").getAsInt()).isEqualTo(4); 39 | } 40 | 41 | @Test 42 | public void convertFromTextDateThroughConverterToSimpleTimestamp() { 43 | // given 44 | TextToLocalDateResolver textToLocalDateResolver = new TextToLocalDateResolver(); 45 | LocalDateToJsonTypeConverter localDateToJsonTypeConverter = new LocalDateToJsonTypeConverter(true); 46 | PropertiesToJsonConverter converter = PropertiesToJsonConverterBuilder.builder() 47 | .onlyCustomTextToObjectResolvers(textToLocalDateResolver) 48 | .onlyCustomObjectToJsonTypeConverters(localDateToJsonTypeConverter) 49 | .build(); 50 | Map properties = new HashMap<>(); 51 | properties.put("object.localDateField", "04-08-2019"); 52 | // when 53 | String json = converter.convertToJson(properties); 54 | // then 55 | System.out.println(json); 56 | JsonElement jsonElement = jp.parse(json); 57 | JsonObject asJsonObject = jsonElement.getAsJsonObject().getAsJsonObject("object"); 58 | assertThat(asJsonObject.getAsJsonPrimitive("localDateField").getAsInt()).isEqualTo(1564876800); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/main/java/pl/jalokim/propertiestojson/object/ObjectJsonType.java: -------------------------------------------------------------------------------- 1 | package pl.jalokim.propertiestojson.object; 2 | 3 | import static pl.jalokim.propertiestojson.Constants.EMPTY_STRING; 4 | import static pl.jalokim.propertiestojson.Constants.JSON_OBJECT_END; 5 | import static pl.jalokim.propertiestojson.Constants.JSON_OBJECT_START; 6 | import static pl.jalokim.propertiestojson.Constants.NEW_LINE_SIGN; 7 | import static pl.jalokim.propertiestojson.object.MergableObject.mergeObjectIfPossible; 8 | import static pl.jalokim.utils.collection.CollectionUtils.getLastIndex; 9 | 10 | import java.util.LinkedHashMap; 11 | import java.util.Map; 12 | import pl.jalokim.propertiestojson.path.PathMetadata; 13 | import pl.jalokim.propertiestojson.util.StringToJsonStringWrapper; 14 | import pl.jalokim.propertiestojson.util.exception.CannotOverrideFieldException; 15 | 16 | public class ObjectJsonType extends AbstractJsonType implements MergableObject { 17 | 18 | @SuppressWarnings("PMD.UseConcurrentHashMap") 19 | private final Map fields = new LinkedHashMap<>(); 20 | 21 | public void addField(final String field, final AbstractJsonType object, PathMetadata currentPathMetaData) { 22 | if (object instanceof SkipJsonField) { 23 | return; 24 | } 25 | 26 | AbstractJsonType oldFieldValue = fields.get(field); 27 | if (oldFieldValue == null) { 28 | fields.put(field, object); 29 | } else { 30 | if (oldFieldValue instanceof MergableObject && object instanceof MergableObject) { 31 | mergeObjectIfPossible(oldFieldValue, object, currentPathMetaData); 32 | } else { 33 | throw new CannotOverrideFieldException(currentPathMetaData.getCurrentFullPath(), 34 | oldFieldValue, 35 | currentPathMetaData.getOriginalPropertyKey()); 36 | } 37 | } 38 | } 39 | 40 | public boolean containsField(String field) { 41 | return fields.containsKey(field); 42 | } 43 | 44 | public AbstractJsonType getField(String field) { 45 | return fields.get(field); 46 | } 47 | 48 | public ArrayJsonType getJsonArray(String field) { 49 | return (ArrayJsonType) fields.get(field); 50 | } 51 | 52 | @Override 53 | public String toStringJson() { 54 | StringBuilder result = new StringBuilder().append(JSON_OBJECT_START); 55 | int index = 0; 56 | int lastIndex = getLastIndex(fields.keySet()); 57 | for (Map.Entry entry : fields.entrySet()) { 58 | AbstractJsonType object = entry.getValue(); 59 | String lastSign = index == lastIndex ? EMPTY_STRING : NEW_LINE_SIGN; 60 | result.append(StringToJsonStringWrapper.wrap(entry.getKey())) 61 | .append(':') 62 | .append(object.toStringJson()) 63 | .append(lastSign); 64 | index++; 65 | } 66 | result.append(JSON_OBJECT_END); 67 | return result.toString(); 68 | } 69 | 70 | @Override 71 | public void merge(ObjectJsonType mergeWith, PathMetadata currentPathMetadata) { 72 | for (String fieldName : mergeWith.fields.keySet()) { 73 | addField(fieldName, mergeWith.getField(fieldName), currentPathMetadata); 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/main/java/pl/jalokim/propertiestojson/JsonObjectFieldsValidator.java: -------------------------------------------------------------------------------- 1 | package pl.jalokim.propertiestojson; 2 | 3 | import pl.jalokim.propertiestojson.object.AbstractJsonType; 4 | import pl.jalokim.propertiestojson.object.ArrayJsonType; 5 | import pl.jalokim.propertiestojson.object.JsonNullReferenceType; 6 | import pl.jalokim.propertiestojson.object.MergableObject; 7 | import pl.jalokim.propertiestojson.object.ObjectJsonType; 8 | import pl.jalokim.propertiestojson.object.PrimitiveJsonType; 9 | import pl.jalokim.propertiestojson.path.PathMetadata; 10 | import pl.jalokim.propertiestojson.util.exception.CannotOverrideFieldException; 11 | 12 | public final class JsonObjectFieldsValidator { 13 | 14 | private JsonObjectFieldsValidator() { 15 | } 16 | 17 | public static void checkThatFieldCanBeSet(ObjectJsonType currentObjectJson, PathMetadata currentPathMetaData, String propertyKey) { 18 | if (currentObjectJson.containsField(currentPathMetaData.getFieldName())) { 19 | AbstractJsonType abstractJsonType = currentObjectJson.getField(currentPathMetaData.getFieldName()); 20 | if (currentPathMetaData.isArrayField()) { 21 | if (isArrayJson(abstractJsonType)) { 22 | ArrayJsonType jsonArray = currentObjectJson.getJsonArray(currentPathMetaData.getFieldName()); 23 | AbstractJsonType elementByDimArray = jsonArray.getElementByGivenDimIndexes(currentPathMetaData); 24 | if (elementByDimArray != null) { 25 | throwErrorWhenCannotMerge(currentPathMetaData, propertyKey, elementByDimArray); 26 | } 27 | } else { 28 | throw new CannotOverrideFieldException(currentPathMetaData.getCurrentFullPathWithoutIndexes(), abstractJsonType, propertyKey); 29 | } 30 | } else { 31 | throwErrorWhenCannotMerge(currentPathMetaData, propertyKey, abstractJsonType); 32 | } 33 | } 34 | } 35 | 36 | private static void throwErrorWhenCannotMerge(PathMetadata currentPathMetaData, String propertyKey, AbstractJsonType oldJsonValue) { 37 | if (!isMergableJsonType(oldJsonValue)) { 38 | throw new CannotOverrideFieldException(currentPathMetaData.getCurrentFullPath(), oldJsonValue, propertyKey); 39 | } 40 | } 41 | 42 | public static void checkEarlierWasJsonObject(String propertyKey, PathMetadata currentPathMetaData, AbstractJsonType jsonType) { 43 | if (!isObjectJson(jsonType)) { 44 | throw new CannotOverrideFieldException(currentPathMetaData.getCurrentFullPath(), jsonType, propertyKey); 45 | } 46 | } 47 | 48 | public static boolean isObjectJson(AbstractJsonType jsonType) { 49 | return ObjectJsonType.class.isAssignableFrom(jsonType.getClass()); 50 | } 51 | 52 | public static boolean isPrimitiveValue(AbstractJsonType jsonType) { 53 | return PrimitiveJsonType.class.isAssignableFrom(jsonType.getClass()) || JsonNullReferenceType.class.isAssignableFrom(jsonType.getClass()); 54 | } 55 | 56 | public static boolean isArrayJson(AbstractJsonType jsonType) { 57 | return ArrayJsonType.class.isAssignableFrom(jsonType.getClass()); 58 | } 59 | 60 | public static boolean isMergableJsonType(Object jsonType) { 61 | return MergableObject.class.isAssignableFrom(jsonType.getClass()); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/pl/jalokim/propertiestojson/JsonObjectsTraverseResolver.java: -------------------------------------------------------------------------------- 1 | package pl.jalokim.propertiestojson; 2 | 3 | import java.util.Map; 4 | import pl.jalokim.propertiestojson.object.AbstractJsonType; 5 | import pl.jalokim.propertiestojson.object.ObjectJsonType; 6 | import pl.jalokim.propertiestojson.object.SkipJsonField; 7 | import pl.jalokim.propertiestojson.path.PathMetadata; 8 | import pl.jalokim.propertiestojson.resolvers.JsonTypeResolver; 9 | import pl.jalokim.propertiestojson.resolvers.PrimitiveJsonTypesResolver; 10 | import pl.jalokim.propertiestojson.resolvers.transfer.DataForResolve; 11 | 12 | public class JsonObjectsTraverseResolver { 13 | 14 | private final Map algorithms; 15 | private final PrimitiveJsonTypesResolver primitiveJsonTypesResolver; 16 | private final Map properties; 17 | private final String propertyKey; 18 | private final PathMetadata rootPathMetaData; 19 | private ObjectJsonType currentObjectJsonType; 20 | 21 | public JsonObjectsTraverseResolver(Map algorithms, 22 | Map properties, String propertyKey, 23 | PathMetadata rootPathMetaData, ObjectJsonType coreObjectJsonType) { 24 | this.properties = properties; 25 | this.propertyKey = propertyKey; 26 | this.rootPathMetaData = rootPathMetaData; 27 | this.currentObjectJsonType = coreObjectJsonType; 28 | this.algorithms = algorithms; 29 | this.primitiveJsonTypesResolver = (PrimitiveJsonTypesResolver) algorithms.get(AlgorithmType.PRIMITIVE); 30 | } 31 | 32 | public void initializeFieldsInJson() { 33 | PathMetadata currentPathMetaData = rootPathMetaData; 34 | Object valueFromProperties = properties.get(currentPathMetaData.getOriginalPropertyKey()); 35 | if (valueFromProperties instanceof SkipJsonField) { 36 | return; 37 | 38 | } 39 | AbstractJsonType resolverJsonObject = primitiveJsonTypesResolver 40 | .resolvePrimitiveTypeAndReturn(valueFromProperties, currentPathMetaData.getOriginalPropertyKey()); 41 | if (resolverJsonObject instanceof SkipJsonField && !rootPathMetaData.getLeaf().isArrayField()) { 42 | return; 43 | } else { 44 | rootPathMetaData.getLeaf().setJsonValue(resolverJsonObject); 45 | } 46 | rootPathMetaData.getLeaf().setRawValue(properties.get(propertyKey)); 47 | 48 | while (currentPathMetaData != null) { 49 | DataForResolve dataForResolve = new DataForResolve(properties, propertyKey, currentObjectJsonType, currentPathMetaData); 50 | currentObjectJsonType = algorithms.get(resolveAlgorithm(currentPathMetaData)) 51 | .traverseOnObjectAndInitByField(dataForResolve); 52 | currentPathMetaData = currentPathMetaData.getChild(); 53 | } 54 | } 55 | 56 | private AlgorithmType resolveAlgorithm(PathMetadata currentPathMetaData) { 57 | if (isPrimitiveField(currentPathMetaData)) { 58 | return AlgorithmType.PRIMITIVE; 59 | } 60 | if (currentPathMetaData.isArrayField()) { 61 | return AlgorithmType.ARRAY; 62 | } 63 | return AlgorithmType.OBJECT; 64 | } 65 | 66 | private boolean isPrimitiveField(PathMetadata currentPathMetaData) { 67 | return currentPathMetaData.isLeaf(); 68 | } 69 | 70 | } 71 | -------------------------------------------------------------------------------- /src/main/java/pl/jalokim/propertiestojson/resolvers/primitives/object/SuperObjectToJsonTypeConverter.java: -------------------------------------------------------------------------------- 1 | package pl.jalokim.propertiestojson.resolvers.primitives.object; 2 | 3 | import static pl.jalokim.propertiestojson.object.JsonNullReferenceType.NULL_OBJECT; 4 | import static pl.jalokim.propertiestojson.resolvers.primitives.utils.JsonObjectHelper.createArrayJsonType; 5 | import static pl.jalokim.propertiestojson.resolvers.primitives.utils.JsonObjectHelper.createObjectJsonType; 6 | import static pl.jalokim.propertiestojson.resolvers.primitives.utils.JsonObjectHelper.toJson; 7 | import static pl.jalokim.propertiestojson.resolvers.primitives.utils.JsonObjectHelper.toJsonElement; 8 | 9 | import com.google.gson.JsonElement; 10 | import java.util.Optional; 11 | import pl.jalokim.propertiestojson.object.AbstractJsonType; 12 | import pl.jalokim.propertiestojson.object.StringJsonType; 13 | import pl.jalokim.propertiestojson.resolvers.PrimitiveJsonTypesResolver; 14 | 15 | public class SuperObjectToJsonTypeConverter extends AbstractObjectToJsonTypeConverter { 16 | 17 | /** 18 | * It convert to implementation of AbstractJsonType through use of json for conversion from java object to raw json, then raw json convert to 19 | * com.google.gson.JsonElement, and this JsonElement to instance of AbstractJsonType (json object, array json, or simple text json) 20 | * 21 | * @param propertyValue java bean to convert to instance of AbstractJsonType. 22 | * @param propertyKey currently processed propertyKey from properties. 23 | * @return instance of AbstractJsonType 24 | */ 25 | public static AbstractJsonType convertFromObjectToJson(Object propertyValue, String propertyKey) { 26 | return convertToObjectArrayOrJsonText(toJsonElement(toJson(propertyValue)), propertyKey); 27 | } 28 | 29 | private static AbstractJsonType convertToObjectArrayOrJsonText(JsonElement someField, String propertyKey) { 30 | AbstractJsonType valueOfNextField = null; 31 | if (someField.isJsonNull()) { 32 | valueOfNextField = NULL_OBJECT; 33 | } 34 | if (someField.isJsonObject()) { 35 | valueOfNextField = createObjectJsonType(someField, propertyKey); 36 | } 37 | if (someField.isJsonArray()) { 38 | valueOfNextField = createArrayJsonType(someField, propertyKey); 39 | } 40 | if (someField.isJsonPrimitive()) { 41 | return new StringJsonType(someField.toString()); 42 | } 43 | return valueOfNextField; 44 | } 45 | 46 | /** 47 | * It return instance of ArrayJsonType or ObjectJsonType if propertyValue is not one of above types then will convert it to gson JsonElement instance and 48 | * then convert to ArrayJsonType, ObjectJsonType, StringJsonType 49 | * 50 | * @param primitiveJsonTypesResolver resolver which is main resolver for leaf value from property 51 | * @param propertyValue property key. 52 | * @return instance of ArrayJsonType, ObjectJsonType or StringJsonType 53 | */ 54 | 55 | @Override 56 | public Optional convertToJsonTypeOrEmpty(PrimitiveJsonTypesResolver primitiveJsonTypesResolver, 57 | Object propertyValue, 58 | String propertyKey) { 59 | if (AbstractJsonType.class.isAssignableFrom(propertyValue.getClass())) { 60 | return Optional.of((AbstractJsonType) propertyValue); 61 | } 62 | return Optional.of(convertFromObjectToJson(propertyValue, propertyKey)); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/main/java/pl/jalokim/propertiestojson/path/PathMetadata.java: -------------------------------------------------------------------------------- 1 | package pl.jalokim.propertiestojson.path; 2 | 3 | import static pl.jalokim.propertiestojson.Constants.EMPTY_STRING; 4 | import static pl.jalokim.propertiestojson.Constants.NORMAL_DOT; 5 | 6 | import lombok.Data; 7 | import pl.jalokim.propertiestojson.PropertyArrayHelper; 8 | import pl.jalokim.propertiestojson.exception.NotLeafValueException; 9 | import pl.jalokim.propertiestojson.object.AbstractJsonType; 10 | 11 | @Data 12 | public class PathMetadata { 13 | 14 | private static final String NUMBER_PATTERN = "([1-9]\\d*)|0"; 15 | public static final String INDEXES_PATTERN = "\\s*(\\[\\s*((" + NUMBER_PATTERN + ")|\\*)\\s*]\\s*)+"; 16 | 17 | private static final String WORD_PATTERN = "(.)*"; 18 | 19 | private final String originalPropertyKey; 20 | private PathMetadata parent; 21 | private String fieldName; 22 | private String originalFieldName; 23 | private PathMetadata child; 24 | private PropertyArrayHelper propertyArrayHelper; 25 | private Object rawValue; 26 | private AbstractJsonType jsonValue; 27 | 28 | public boolean isLeaf() { 29 | return child == null; 30 | } 31 | 32 | public boolean isRoot() { 33 | return parent == null; 34 | } 35 | 36 | public String getCurrentFullPath() { 37 | return parent == null ? originalFieldName : parent.getCurrentFullPath() + NORMAL_DOT + originalFieldName; 38 | } 39 | 40 | public PathMetadata getLeaf() { 41 | PathMetadata current = this; 42 | while (current.getChild() != null) { 43 | current = current.getChild(); 44 | } 45 | return current; 46 | } 47 | 48 | public void setFieldName(String fieldName) { 49 | this.fieldName = fieldName; 50 | if (fieldName.matches(WORD_PATTERN + INDEXES_PATTERN)) { 51 | propertyArrayHelper = new PropertyArrayHelper(fieldName); 52 | this.fieldName = propertyArrayHelper.getArrayFieldName(); 53 | } 54 | } 55 | 56 | public void setRawValue(Object rawValue) { 57 | if (!isLeaf()) { 58 | throw new NotLeafValueException("Cannot set value for not leaf: " + getCurrentFullPath()); 59 | } 60 | this.rawValue = rawValue; 61 | } 62 | 63 | public String getOriginalPropertyKey() { 64 | return originalPropertyKey; 65 | } 66 | 67 | public PathMetadata getRoot() { 68 | PathMetadata current = this; 69 | while (current.getParent() != null) { 70 | current = current.getParent(); 71 | } 72 | return current; 73 | } 74 | 75 | public String getCurrentFullPathWithoutIndexes() { 76 | String parentFullPath = isRoot() ? EMPTY_STRING : getParent().getCurrentFullPath() + NORMAL_DOT; 77 | return parentFullPath + getFieldName(); 78 | } 79 | 80 | public AbstractJsonType getJsonValue() { 81 | return jsonValue; 82 | } 83 | 84 | public void setJsonValue(AbstractJsonType jsonValue) { 85 | if (!isLeaf()) { 86 | throw new NotLeafValueException("Cannot set value for not leaf: " + getCurrentFullPath()); 87 | } 88 | this.jsonValue = jsonValue; 89 | } 90 | 91 | @Override 92 | public String toString() { 93 | return "field='" + fieldName + '\'' 94 | + ", rawValue=" + rawValue 95 | + ", fullPath='" + getCurrentFullPath() + '}'; 96 | } 97 | 98 | public boolean isArrayField() { 99 | return propertyArrayHelper != null; 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/main/java/pl/jalokim/propertiestojson/resolvers/ArrayJsonTypeResolver.java: -------------------------------------------------------------------------------- 1 | package pl.jalokim.propertiestojson.resolvers; 2 | 3 | import static pl.jalokim.propertiestojson.object.ArrayJsonType.createOrGetNextDimensionOfArray; 4 | import static pl.jalokim.utils.collection.CollectionUtils.isLastIndex; 5 | 6 | import java.util.List; 7 | import pl.jalokim.propertiestojson.JsonObjectFieldsValidator; 8 | import pl.jalokim.propertiestojson.PropertyArrayHelper; 9 | import pl.jalokim.propertiestojson.object.AbstractJsonType; 10 | import pl.jalokim.propertiestojson.object.ArrayJsonType; 11 | import pl.jalokim.propertiestojson.object.ObjectJsonType; 12 | import pl.jalokim.propertiestojson.path.PathMetadata; 13 | 14 | public class ArrayJsonTypeResolver extends JsonTypeResolver { 15 | 16 | @Override 17 | public ObjectJsonType traverse(PathMetadata currentPathMetaData) { 18 | fetchJsonObjectAndCreateArrayWhenNotExist(currentPathMetaData); 19 | return currentObjectJsonType; 20 | } 21 | 22 | private void fetchJsonObjectAndCreateArrayWhenNotExist(PathMetadata currentPathMetaData) { 23 | if (isArrayExist(currentPathMetaData.getFieldName())) { 24 | fetchArrayAndAddElement(currentPathMetaData); 25 | } else { 26 | createArrayAndAddElement(currentPathMetaData); 27 | } 28 | } 29 | 30 | private boolean isArrayExist(String field) { 31 | return currentObjectJsonType.containsField(field); 32 | } 33 | 34 | private void fetchArrayAndAddElement(PathMetadata currentPathMetaData) { 35 | PropertyArrayHelper propertyArrayHelper = currentPathMetaData.getPropertyArrayHelper(); 36 | ArrayJsonType arrayJsonType = getArrayJsonWhenIsValid(currentPathMetaData); 37 | List dimIndexes = propertyArrayHelper.getDimensionalIndexes(); 38 | ArrayJsonType currentArray = arrayJsonType; 39 | for (int index = 0; index < dimIndexes.size(); index++) { 40 | if (isLastIndex(dimIndexes, index)) { 41 | int lastDimIndex = dimIndexes.get(index); 42 | if (currentArray.existElementByGivenIndex(lastDimIndex)) { 43 | fetchJsonObjectWhenIsValid(currentPathMetaData, lastDimIndex, currentArray); 44 | } else { 45 | createJsonObjectAndAddToArray(lastDimIndex, currentArray, currentPathMetaData); 46 | } 47 | } else { 48 | currentArray = createOrGetNextDimensionOfArray(currentArray, dimIndexes, index, currentPathMetaData); 49 | } 50 | } 51 | } 52 | 53 | private void createJsonObjectAndAddToArray(int index, ArrayJsonType arrayJsonType, PathMetadata currentPathMetaData) { 54 | ObjectJsonType nextObjectJsonType = new ObjectJsonType(); 55 | arrayJsonType.addElement(index, nextObjectJsonType, currentPathMetaData); 56 | currentObjectJsonType = nextObjectJsonType; 57 | } 58 | 59 | private void fetchJsonObjectWhenIsValid(PathMetadata currentPathMetaData, int index, ArrayJsonType arrayJsonType) { 60 | AbstractJsonType element = arrayJsonType.getElement(index); 61 | JsonObjectFieldsValidator.checkEarlierWasJsonObject(currentPathMetaData.getOriginalPropertyKey(), currentPathMetaData, element); 62 | currentObjectJsonType = (ObjectJsonType) element; 63 | } 64 | 65 | private void createArrayAndAddElement(PathMetadata currentPathMetaData) { 66 | ArrayJsonType arrayJsonTypeObject = new ArrayJsonType(); 67 | ObjectJsonType nextObjectJsonType = new ObjectJsonType(); 68 | arrayJsonTypeObject.addElement(currentPathMetaData.getPropertyArrayHelper(), nextObjectJsonType, currentPathMetaData); 69 | currentObjectJsonType.addField(currentPathMetaData.getFieldName(), arrayJsonTypeObject, currentPathMetaData); 70 | currentObjectJsonType = nextObjectJsonType; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/test/java/pl/jalokim/propertiestojson/util/PropertiesToJsonConverterFilterTest.java: -------------------------------------------------------------------------------- 1 | package pl.jalokim.propertiestojson.util; 2 | 3 | import com.google.gson.Gson; 4 | import java.io.IOException; 5 | import java.io.InputStream; 6 | import org.assertj.core.api.Assertions; 7 | import org.junit.Test; 8 | import pl.jalokim.propertiestojson.domain.MainObject; 9 | 10 | public class PropertiesToJsonConverterFilterTest extends AbstractPropertiesToJsonConverterTest { 11 | 12 | @Test 13 | public void parsePropertiesOnlyByIncludedKeys() { 14 | //when 15 | String json = new PropertiesToJsonConverter().convertToJson(initProperlyPropertiesMap(), "man.groups", "man.hoobies", "insurance.cost"); 16 | // then 17 | assertJsonIsAsExpected(json); 18 | } 19 | 20 | @Test 21 | public void parseInputStreamOnlyByIncludedKeys() throws IOException { 22 | // given 23 | InputStream inputStream = getPropertiesFromFile(); 24 | // when 25 | String json = new PropertiesToJsonConverter().convertToJson(inputStream, "man.groups", "man.hoobies", "insurance.cost"); 26 | // then 27 | assertJsonIsAsExpected(json); 28 | } 29 | 30 | private void assertJsonIsAsExpected(String json) { 31 | Gson gson = new Gson(); 32 | MainObject mainObject = gson.fromJson(json, MainObject.class); 33 | Assertions.assertThat(mainObject.getField1()).isNull(); 34 | Assertions.assertThat(mainObject.getField2()).isNull(); 35 | Assertions.assertThat(mainObject.getInsurance().getCost()).isEqualTo(COST_INT_VALUE); 36 | Assertions.assertThat(mainObject.getInsurance().getType()).isNull(); 37 | Assertions.assertThat(mainObject.getMan().getAddress()).isNull(); 38 | Assertions.assertThat(mainObject.getMan().getName()).isNull(); 39 | Assertions.assertThat(mainObject.getMan().getSurname()).isNull(); 40 | Assertions.assertThat(mainObject.getMan().getInsurance()).isNull(); 41 | Assertions.assertThat(mainObject.getMan().getEmails()).isNull(); 42 | assertGroupByIdAndExpectedValues(mainObject, 0, GROUP_1, COMMERCIAL); 43 | assertGroupByIdAndExpectedValues(mainObject, 1, GROUP_2, FREE); 44 | assertGroupByIdAndExpectedValues(mainObject, 2, GROUP_3, COMMERCIAL); 45 | assertHobbiesList(mainObject); 46 | } 47 | 48 | @Test 49 | public void parseInputStreamOnlyWithWholeInsuranceByIncludedKeys() throws IOException { 50 | // given 51 | InputStream inputStream = getPropertiesFromFile(); 52 | // when 53 | String json = new PropertiesToJsonConverter().convertToJson(inputStream, "man.groups", "man.hoobies", "insurance"); 54 | // then 55 | assertJsonIsAsExpectedWithWholeInsurance(json); 56 | } 57 | 58 | private void assertJsonIsAsExpectedWithWholeInsurance(String json) { 59 | Gson gson = new Gson(); 60 | MainObject mainObject = gson.fromJson(json, MainObject.class); 61 | Assertions.assertThat(mainObject.getField1()).isNull(); 62 | Assertions.assertThat(mainObject.getField2()).isNull(); 63 | Assertions.assertThat(mainObject.getInsurance().getCost()).isEqualTo(COST_INT_VALUE); 64 | Assertions.assertThat(mainObject.getInsurance().getType()).isEqualTo("Medical"); 65 | Assertions.assertThat(mainObject.getMan().getAddress()).isNull(); 66 | Assertions.assertThat(mainObject.getMan().getName()).isNull(); 67 | Assertions.assertThat(mainObject.getMan().getSurname()).isNull(); 68 | Assertions.assertThat(mainObject.getMan().getInsurance()).isNull(); 69 | Assertions.assertThat(mainObject.getMan().getEmails()).isNull(); 70 | assertGroupByIdAndExpectedValues(mainObject, 0, GROUP_1, COMMERCIAL); 71 | assertGroupByIdAndExpectedValues(mainObject, 1, GROUP_2, FREE); 72 | assertGroupByIdAndExpectedValues(mainObject, 2, GROUP_3, COMMERCIAL); 73 | assertHobbiesList(mainObject); 74 | } 75 | 76 | } 77 | -------------------------------------------------------------------------------- /src/main/java/pl/jalokim/propertiestojson/resolvers/primitives/object/ObjectToJsonTypeConverter.java: -------------------------------------------------------------------------------- 1 | package pl.jalokim.propertiestojson.resolvers.primitives.object; 2 | 3 | import java.util.List; 4 | import java.util.Optional; 5 | import pl.jalokim.propertiestojson.object.AbstractJsonType; 6 | import pl.jalokim.propertiestojson.object.JsonNullReferenceType; 7 | import pl.jalokim.propertiestojson.object.SkipJsonField; 8 | import pl.jalokim.propertiestojson.resolvers.PrimitiveJsonTypesResolver; 9 | import pl.jalokim.propertiestojson.resolvers.hierarchy.JsonTypeResolversHierarchyResolver; 10 | 11 | /** 12 | * You can extends {@link AbstractObjectToJsonTypeConverter} which have implemented {@link AbstractObjectToJsonTypeConverter#classesWhichCanResolved} 13 | * 14 | * @param Generic type of converter. 15 | */ 16 | public interface ObjectToJsonTypeConverter extends HasGenericType { 17 | 18 | @SuppressWarnings("unchecked") 19 | default Optional returnOptionalJsonType(PrimitiveJsonTypesResolver primitiveJsonTypesResolver, Object propertyValue, String propertyKey) { 20 | return convertToJsonTypeOrEmpty(primitiveJsonTypesResolver, (T) propertyValue, propertyKey); 21 | } 22 | 23 | /** 24 | * This method will be called in second phase conversion step (from some java Object to some implementation of AbstractJsonType) it will be called during 25 | * read properties from Map<String,Object>, Properties (without first processing step) or after first conversion phase (while reading properties from 26 | * file, Map<String,String>, inputStream) 27 | *

28 | * But converters order (provided in PropertiesToJsonConverter constructor for List<ObjectToJsonTypeConverter> toJsonTypeResolvers or through 29 | * PropertiesToJsonConverterBuilder) doesn't have importance here as in first processing phase, it is important only when some of implementation of {@link 30 | * pl.jalokim.propertiestojson.resolvers.primitives.object.ObjectToJsonTypeConverter} can convert from the same java class, then order or the same 31 | * converters type have matter. But mostly hierarchy of classes plays a main role here It looks for sufficient resolver, firstly will looks for exactly 32 | * match class type provided by method {@link pl.jalokim.propertiestojson.resolvers.primitives.object.ObjectToJsonTypeConverter#getClassesWhichCanResolve()} 33 | * if find a few resolvers for the same class then it will looks for firs converter which properly convert java object to AbstractJsonType (here converters 34 | * order does it matter). More here {@link JsonTypeResolversHierarchyResolver} 35 | *

36 | * 37 | * AbstractJsonType should contains converted data and provides implementation for "toStringJson()" method if you provide your own... or you can return 38 | * instance of existence one implementation in package 'pl.jalokim.propertiestojson.object'... number, boolean, text, primitive array, json objects... or 39 | * simply convert Java object to instance ObjectJsonType by static method: public static AbstractJsonType convertFromObjectToJson(Object propertyValue, 40 | * String propertyKey) {@link SuperObjectToJsonTypeConverter#convertFromObjectToJson(Object propertyValue, String propertyKey)} Or if you want return null 41 | * json object then return instance of {@link JsonNullReferenceType#NULL_OBJECT} Or if you want to skip this json leaf then return instance of {@link 42 | * SkipJsonField#SKIP_JSON_FIELD} then it will not add it to json with null value. 43 | * 44 | * @param primitiveJsonTypesResolver primitiveJsonTypesResolver 45 | * @param convertedValue currently processing property value but as generic type 46 | * @param propertyKey currently processing property key 47 | * @return optional value 48 | */ 49 | Optional convertToJsonTypeOrEmpty(PrimitiveJsonTypesResolver primitiveJsonTypesResolver, 50 | T convertedValue, 51 | String propertyKey); 52 | 53 | /** 54 | * Override it when want inform about what type of classes can coverts this converter. 55 | * 56 | * @return list of classes. 57 | */ 58 | List> getClassesWhichCanResolve(); 59 | } 60 | -------------------------------------------------------------------------------- /src/main/java/pl/jalokim/propertiestojson/resolvers/primitives/adapter/PrimitiveJsonTypeResolverToNewApiAdapter.java: -------------------------------------------------------------------------------- 1 | package pl.jalokim.propertiestojson.resolvers.primitives.adapter; 2 | 3 | import static java.util.Arrays.asList; 4 | import static pl.jalokim.utils.reflection.InvokableReflectionUtils.invokeMethod; 5 | import static pl.jalokim.utils.reflection.InvokableReflectionUtils.setValueForField; 6 | 7 | import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; 8 | import java.util.List; 9 | import java.util.Optional; 10 | import pl.jalokim.propertiestojson.object.AbstractJsonType; 11 | import pl.jalokim.propertiestojson.resolvers.PrimitiveJsonTypesResolver; 12 | import pl.jalokim.propertiestojson.resolvers.primitives.PrimitiveJsonTypeResolver; 13 | import pl.jalokim.propertiestojson.resolvers.primitives.object.ObjectToJsonTypeConverter; 14 | import pl.jalokim.propertiestojson.resolvers.primitives.string.TextToConcreteObjectResolver; 15 | 16 | @SuppressWarnings("unchecked") 17 | @SuppressFBWarnings("UR_UNINIT_READ_CALLED_FROM_SUPER_CONSTRUCTOR") 18 | public final class PrimitiveJsonTypeResolverToNewApiAdapter extends PrimitiveJsonTypeResolver 19 | implements TextToConcreteObjectResolver, ObjectToJsonTypeConverter { 20 | 21 | private final PrimitiveJsonTypeResolver oldImplementation; 22 | 23 | public PrimitiveJsonTypeResolverToNewApiAdapter(PrimitiveJsonTypeResolver oldImplementation) { 24 | this.oldImplementation = (PrimitiveJsonTypeResolver) oldImplementation; 25 | setValueForField(this, "typeWhichCanBeResolved", resolveTypeOfResolver()); 26 | } 27 | 28 | @Override // from PrimitiveJsonTypeResolver and ObjectToJsonTypeConverter 29 | public Class resolveTypeOfResolver() { 30 | if (oldImplementation != null) { 31 | return oldImplementation.resolveTypeOfResolver(); 32 | } 33 | return null; 34 | } 35 | 36 | @Override // from PrimitiveJsonTypeResolver and ObjectToJsonTypeConverter 37 | public AbstractJsonType returnJsonType(PrimitiveJsonTypesResolver primitiveJsonTypesResolver, 38 | Object propertyValue, 39 | String propertyKey) { 40 | return oldImplementation.returnJsonType(primitiveJsonTypesResolver, 41 | propertyValue, 42 | propertyKey); 43 | } 44 | 45 | @Override // from PrimitiveJsonTypeResolver 46 | protected Optional returnConcreteValueWhenCanBeResolved(PrimitiveJsonTypesResolver primitiveJsonTypesResolver, 47 | String propertyValue, 48 | String propertyKey) { 49 | return invokeMethod(oldImplementation, "returnConcreteValueWhenCanBeResolved", 50 | asList(PrimitiveJsonTypesResolver.class, String.class, String.class), 51 | asList(primitiveJsonTypesResolver, propertyValue, propertyKey)); 52 | } 53 | 54 | @Override // from PrimitiveJsonTypeResolver 55 | public AbstractJsonType returnConcreteJsonType(PrimitiveJsonTypesResolver primitiveJsonTypesResolver, 56 | Object convertedValue, 57 | String propertyKey) { 58 | return oldImplementation.returnConcreteJsonType(primitiveJsonTypesResolver, convertedValue, propertyKey); 59 | } 60 | 61 | @Override // from TextToConcreteObjectResolver and PrimitiveJsonTypeResolver 62 | public Optional returnConvertedValueForClearedText(PrimitiveJsonTypesResolver primitiveJsonTypesResolver, 63 | String propertyValue, 64 | String propertyKey) { 65 | Optional optional = oldImplementation.returnConvertedValueForClearedText(primitiveJsonTypesResolver, propertyValue, propertyKey); 66 | return Optional.ofNullable(optional.orElse(null)); 67 | } 68 | 69 | @Override // from TextToConcreteObjectResolver 70 | public Optional returnObjectWhenCanBeResolved(PrimitiveJsonTypesResolver primitiveJsonTypesResolver, 71 | String propertyValue, 72 | String propertyKey) { 73 | Optional optional = returnConcreteValueWhenCanBeResolved(primitiveJsonTypesResolver, propertyValue, propertyKey); 74 | return Optional.ofNullable(optional.orElse(null)); 75 | } 76 | 77 | @Override // from ObjectToJsonTypeConverter 78 | public Optional convertToJsonTypeOrEmpty(PrimitiveJsonTypesResolver primitiveJsonTypesResolver, Object convertedValue, 79 | String propertyKey) { 80 | return Optional.of(oldImplementation.returnJsonType(primitiveJsonTypesResolver, convertedValue, propertyKey)); 81 | } 82 | 83 | @Override // from ObjectToJsonTypeConverter and PrimitiveJsonTypeResolver 84 | public List> getClassesWhichCanResolve() { 85 | return oldImplementation.getClassesWhichCanResolve(); 86 | } 87 | 88 | public PrimitiveJsonTypeResolver getOldImplementation() { 89 | return oldImplementation; 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/test/groovy/pl/jalokim/propertiestojson/util/PropertiesToJsonConverterMixinObjectPropertiesTest.groovy: -------------------------------------------------------------------------------- 1 | package pl.jalokim.propertiestojson.util 2 | 3 | import static pl.jalokim.propertiestojson.util.PropertiesToJsonParsePropertiesExceptionTest.setUpMockPickupKeysOrder 4 | 5 | import groovy.json.JsonSlurper 6 | import pl.jalokim.propertiestojson.helper.PropertyKeysOrderResolverForTest 7 | import spock.lang.Specification 8 | 9 | class PropertiesToJsonConverterMixinObjectPropertiesTest extends Specification { 10 | 11 | private static final String CITY_VALUE = "New York" 12 | private static final String MAN_ADDRESS_PATH = "man.address" 13 | private static final String MAN_ADDRESS_CITY_PATH = "man.address.city" 14 | 15 | def jsonSlurper = new JsonSlurper() 16 | 17 | def "can merge json object with json object from text(from primitive value)"() { 18 | when: 19 | PropertiesToJsonConverter converter = new PropertiesToJsonConverter() 20 | PropertyKeysOrderResolverForTest propertiesResolver = new PropertyKeysOrderResolverForTest() 21 | propertiesResolver.setUpMockKeys(MAN_ADDRESS_CITY_PATH, MAN_ADDRESS_PATH) 22 | converter.propertyKeysOrderResolver = propertiesResolver 23 | String json = converter.convertToJson(getProperties()) 24 | println(json) 25 | def jsonObject = jsonSlurper.parseText(json) 26 | then: 27 | jsonObject.man.address.city == CITY_VALUE 28 | jsonObject.man.address.barCode == "01-103" 29 | jsonObject.man.address.street == "12" 30 | } 31 | 32 | def throwWhenCannotOverrideArrayElementByObjectType() { 33 | given: 34 | PropertiesToJsonConverter converter = new PropertiesToJsonConverter() 35 | setUpMockPickupKeysOrder(converter, 36 | "some.someArray[0].someObject.someArray[4]", 37 | "some.someArray[0].someObject.someArray[3]", 38 | "some.someArray[0].field", 39 | "some.someArray[0]", 40 | "some.someArray[0].someObject", 41 | "some.someArray[0].objectField", 42 | "some.someArray[0].someObject.anotherField" 43 | ) 44 | 45 | Map properties = [:] 46 | properties.put("some.someArray[0].field", "simpleText") 47 | properties.put("some.someArray[0]", "{\"objectField\": {\"nextField\": 2},\n\"someSimpleField\": \"text\"\n}") 48 | properties.put("some.someArray[0].objectField", "{\"nextField_3\": 3,\n \"someSimpleField_T\": true}") 49 | properties.put("some.someArray[0].someObject.anotherField", "{\"someField\": 2.2}") 50 | properties.put("some.someArray[0].someObject.someArray[3]", "{\"field\": true}") 51 | properties.put("some.someArray[0].someObject.someArray[4]", "{\"text\": \"test_*8\"}") 52 | properties.put("some.someArray[0].someObject", "{\"someArray\": [true, 12], \"nextSimpField\": 12.123}") 53 | 54 | when: 55 | String json = converter.convertToJson(properties) 56 | println(json) 57 | def jsonObject = jsonSlurper.parseText(json) 58 | then: 59 | def someElement = jsonObject.some.someArray[0] 60 | someElement.field == "simpleText" 61 | someElement.objectField.nextField == 2 62 | someElement.objectField.nextField_3 == 3 63 | someElement.objectField.someSimpleField_T == true 64 | someElement.someSimpleField == "text" 65 | 66 | someElement.someObject.anotherField.someField == 2.2 67 | someElement.someObject.someArray[0] == true 68 | someElement.someObject.someArray[1] == 12 69 | someElement.someObject.someArray[2] == null 70 | someElement.someObject.someArray[3].field == true 71 | someElement.someObject.someArray[4].text == "test_*8" 72 | someElement.someObject.nextSimpField == 12.123 73 | } 74 | 75 | def "can merge json object from text(from primitive value) with json object "() { 76 | when: 77 | PropertiesToJsonConverter converter = new PropertiesToJsonConverter() 78 | PropertyKeysOrderResolverForTest propertiesResolver = new PropertyKeysOrderResolverForTest() 79 | propertiesResolver.setUpMockKeys(MAN_ADDRESS_PATH, MAN_ADDRESS_CITY_PATH) 80 | converter.propertyKeysOrderResolver = propertiesResolver 81 | String json = converter.convertToJson(getProperties()) 82 | println(json) 83 | def jsonObject = jsonSlurper.parseText(json) 84 | then: 85 | jsonObject.man.address.city == CITY_VALUE 86 | jsonObject.man.address.barCode == "01-103" 87 | jsonObject.man.address.street == "12" 88 | } 89 | 90 | private Map getProperties() { 91 | Map map = [:] 92 | map.put(MAN_ADDRESS_CITY_PATH, CITY_VALUE) 93 | map.put(MAN_ADDRESS_PATH, "{\"barCode\":\"01-103\", \"street\": \"12\"}") 94 | map 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/main/java/pl/jalokim/propertiestojson/resolvers/primitives/PrimitiveJsonTypeResolver.java: -------------------------------------------------------------------------------- 1 | package pl.jalokim.propertiestojson.resolvers.primitives; 2 | 3 | import java.util.Collections; 4 | import java.util.List; 5 | import java.util.Optional; 6 | import pl.jalokim.propertiestojson.object.AbstractJsonType; 7 | import pl.jalokim.propertiestojson.object.JsonNullReferenceType; 8 | import pl.jalokim.propertiestojson.resolvers.PrimitiveJsonTypesResolver; 9 | import pl.jalokim.propertiestojson.resolvers.hierarchy.JsonTypeResolversHierarchyResolver; 10 | import pl.jalokim.propertiestojson.resolvers.primitives.adapter.InvokedFromAdapter; 11 | import pl.jalokim.propertiestojson.resolvers.primitives.delegator.InvokedFromDelegator; 12 | import pl.jalokim.propertiestojson.resolvers.primitives.object.HasGenericType; 13 | 14 | @SuppressWarnings("unchecked") 15 | @Deprecated 16 | public abstract class PrimitiveJsonTypeResolver implements HasGenericType { 17 | 18 | protected final Class typeWhichCanBeResolved = resolveTypeOfResolver(); 19 | 20 | @InvokedFromAdapter 21 | @InvokedFromDelegator 22 | public AbstractJsonType returnJsonType(PrimitiveJsonTypesResolver primitiveJsonTypesResolver, Object propertyValue, String propertyKey) { 23 | return returnConcreteJsonType(primitiveJsonTypesResolver, (T) propertyValue, propertyKey); 24 | } 25 | 26 | /** 27 | * This method will be called in second phase processing step (from some java Object to some implementation of AbstractJsonType) it will be called during 28 | * read properties from Map<String,Object>, Properties (without first processing step) or after first processing step (while reading properties from 29 | * file, Map<String,String>, inputStream) 30 | *

31 | * But resolvers order (provided in PropertiesToJsonConverter(PrimitiveJsonTypeResolver... primitiveResolvers) constructor) doesn't have importance here as 32 | * in first processing phase. The hierarchy of classes plays a central role here It looks for sufficient resolver, firstly will looks for exactly match 33 | * class type provided by method {@link PrimitiveJsonTypeResolver#getClassesWhichCanResolve()} More here {@link JsonTypeResolversHierarchyResolver} 34 | *

35 | * AbstractJsonType should contains converted data and provides implementation for "toStringJson()" method if you provide you own... or you can return 36 | * instance of existence one implementation in package 'pl.jalokim.propertiestojson.object'... number, boolean, text, primitive array, json objects... or 37 | * simply convert Java object to instance ObjectJsonType by static method: public static AbstractJsonType convertFromObjectToJson(Object propertyValue, 38 | * String propertyKey) {@link ObjectFromTextJsonTypeResolver#convertFromObjectToJson(Object propertyValue, String propertyKey)} Or if you want return null 39 | * json object then return instance of {@link JsonNullReferenceType#NULL_OBJECT} 40 | * 41 | * @param primitiveJsonTypesResolver primitiveJsonTypesResolver 42 | * @param convertedValue currently processing property value but as generic type 43 | * @param propertyKey currently processing property key 44 | * @return optional value 45 | */ 46 | @InvokedFromAdapter 47 | public abstract AbstractJsonType returnConcreteJsonType(PrimitiveJsonTypesResolver primitiveJsonTypesResolver, T convertedValue, String propertyKey); 48 | 49 | @InvokedFromAdapter 50 | @InvokedFromDelegator 51 | public Optional returnConvertedValueForClearedText(PrimitiveJsonTypesResolver primitiveJsonTypesResolver, 52 | String propertyValue, 53 | String propertyKey) { 54 | return returnConcreteValueWhenCanBeResolved(primitiveJsonTypesResolver, 55 | propertyValue == null ? null : propertyValue.trim(), propertyKey); 56 | } 57 | 58 | /** 59 | * This method will be called in first phase processing step (from raw text to some object) if your condition is met then return concrete value of Object. 60 | * if it doesn't meet its condition then return Optional.empty() for allow go to others type resolver in order. This will be called only for read properties 61 | * from Map<String,String>, File with properties, InputStream with properties 62 | * 63 | * @param primitiveJsonTypesResolver primitiveJsonTypesResolver 64 | * @param propertyValue currently processing property value 65 | * @param propertyKey currently processing property key 66 | * @return optional value 67 | */ 68 | @InvokedFromAdapter 69 | protected abstract Optional returnConcreteValueWhenCanBeResolved(PrimitiveJsonTypesResolver primitiveJsonTypesResolver, 70 | String propertyValue, 71 | String propertyKey); 72 | 73 | @InvokedFromAdapter 74 | @InvokedFromDelegator 75 | public List> getClassesWhichCanResolve() { 76 | return Collections.singletonList(typeWhichCanBeResolved); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/main/java/pl/jalokim/propertiestojson/resolvers/primitives/custom/LocalDateToJsonTypeConverter.java: -------------------------------------------------------------------------------- 1 | package pl.jalokim.propertiestojson.resolvers.primitives.custom; 2 | 3 | import java.time.LocalDate; 4 | import java.time.ZoneOffset; 5 | import java.util.Optional; 6 | import pl.jalokim.propertiestojson.object.AbstractJsonType; 7 | import pl.jalokim.propertiestojson.object.JsonNullReferenceType; 8 | import pl.jalokim.propertiestojson.object.NumberJsonType; 9 | import pl.jalokim.propertiestojson.object.SkipJsonField; 10 | import pl.jalokim.propertiestojson.resolvers.PrimitiveJsonTypesResolver; 11 | import pl.jalokim.propertiestojson.resolvers.hierarchy.JsonTypeResolversHierarchyResolver; 12 | import pl.jalokim.propertiestojson.resolvers.primitives.object.AbstractObjectToJsonTypeConverter; 13 | import pl.jalokim.propertiestojson.resolvers.primitives.object.SuperObjectToJsonTypeConverter; 14 | 15 | /** 16 | * results of this resolver you can see in those test classes: 17 | * 18 | * @see LocalDateTimeResolverTest 19 | * @see LocalDateTimeResolverTest 20 | */ 21 | @SuppressWarnings("checkstyle:AbbreviationAsWordInName") 22 | public class LocalDateToJsonTypeConverter extends AbstractObjectToJsonTypeConverter { 23 | 24 | private final boolean asTimestampInUTC; 25 | 26 | public LocalDateToJsonTypeConverter() { 27 | this(false); 28 | } 29 | 30 | public LocalDateToJsonTypeConverter(boolean asTimestampInUTC) { 31 | this.asTimestampInUTC = asTimestampInUTC; 32 | } 33 | 34 | /** 35 | * This method will be called in second phase conversion step (from some java Object to some implementation of AbstractJsonType) it will be called during 36 | * read properties from Map<String,Object>, Properties (without first processing step) or after first conversion phase (while reading properties from 37 | * file, Map<String,String>, inputStream) 38 | *

39 | * But converters order (provided in PropertiesToJsonConverter constructor for List<ObjectToJsonTypeConverter> toJsonTypeResolvers or through 40 | * PropertiesToJsonConverterBuilder) doesn't have importance here as in first processing phase, it is important only when some of implementation of {@link 41 | * pl.jalokim.propertiestojson.resolvers.primitives.object.ObjectToJsonTypeConverter} can convert from the same java class, then order or the same 42 | * converters type have matter. But mostly hierarchy of classes plays a main role here It looks for sufficient resolver, firstly will looks for exactly 43 | * match class type provided by method {@link pl.jalokim.propertiestojson.resolvers.primitives.object.ObjectToJsonTypeConverter#getClassesWhichCanResolve()} 44 | * if find a few resolvers for the same class then it will looks for firs converter which properly convert java object to AbstractJsonType (here converters 45 | * order does it matter). More here {@link JsonTypeResolversHierarchyResolver} 46 | *

47 | * 48 | * AbstractJsonType should contains converted data and provides implementation for "toStringJson()" method if you provide your own... or you can return 49 | * instance of existence one implementation in package 'pl.jalokim.propertiestojson.object'... number, boolean, text, primitive array, json objects... or 50 | * simply convert Java object to instance ObjectJsonType by static method: public static AbstractJsonType convertFromObjectToJson(Object propertyValue, 51 | * String propertyKey) {@link SuperObjectToJsonTypeConverter#convertFromObjectToJson(Object propertyValue, String propertyKey)} Or if you want return null 52 | * json object then return instance of {@link JsonNullReferenceType#NULL_OBJECT} Or if you want to skip this json leaf then return instance of {@link 53 | * SkipJsonField#SKIP_JSON_FIELD} then it will not add it to json with null value. 54 | * 55 | * @param primitiveJsonTypesResolver primitiveJsonTypesResolver 56 | * @param convertedValue currently processing property value but as generic type 57 | * @param propertyKey currently processing property key 58 | * @return optional value 59 | */ 60 | 61 | @Override 62 | public Optional convertToJsonTypeOrEmpty(PrimitiveJsonTypesResolver primitiveJsonTypesResolver, 63 | LocalDate convertedValue, 64 | String propertyKey) { 65 | if (asTimestampInUTC) { 66 | return Optional.of(new NumberJsonType(convertedValue.atStartOfDay(ZoneOffset.UTC).toEpochSecond())); 67 | } else if (!propertyKey.contains("asText")) { 68 | return Optional.of(SuperObjectToJsonTypeConverter.convertFromObjectToJson(convertedValue, propertyKey)); 69 | } 70 | return Optional.empty(); // allow to go to another converter which will convert LocalDate to AbstractJsonType... 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/main/java/pl/jalokim/propertiestojson/resolvers/hierarchy/JsonTypeResolversHierarchyResolver.java: -------------------------------------------------------------------------------- 1 | package pl.jalokim.propertiestojson.resolvers.hierarchy; 2 | 3 | import static java.lang.String.format; 4 | import static java.util.stream.Collectors.toList; 5 | import static pl.jalokim.propertiestojson.util.exception.ParsePropertiesException.CANNOT_FIND_JSON_TYPE_OBJ; 6 | import static pl.jalokim.propertiestojson.util.exception.ParsePropertiesException.CANNOT_FIND_TYPE_RESOLVER_MSG; 7 | 8 | import java.util.ArrayList; 9 | import java.util.HashMap; 10 | import java.util.List; 11 | import java.util.Map; 12 | import java.util.Objects; 13 | import java.util.Optional; 14 | import pl.jalokim.propertiestojson.object.AbstractJsonType; 15 | import pl.jalokim.propertiestojson.resolvers.PrimitiveJsonTypesResolver; 16 | import pl.jalokim.propertiestojson.resolvers.primitives.PrimitiveJsonTypeResolver; 17 | import pl.jalokim.propertiestojson.resolvers.primitives.adapter.PrimitiveJsonTypeResolverToNewApiAdapter; 18 | import pl.jalokim.propertiestojson.resolvers.primitives.object.ObjectToJsonTypeConverter; 19 | import pl.jalokim.propertiestojson.util.exception.ParsePropertiesException; 20 | 21 | /** 22 | * It looks for sufficient resolver, firstly will looks for exactly match class type provided by method {@link 23 | * PrimitiveJsonTypeResolver#getClassesWhichCanResolve()} if not then will looks for closets parent class or parent interface. If will find resolver for parent 24 | * class or parent interface at the same level, then will get parent super class as first. If will find only closets super interfaces (at the same level) then 25 | * will throw exception... 26 | */ 27 | public class JsonTypeResolversHierarchyResolver { 28 | 29 | @SuppressWarnings("PMD.UseConcurrentHashMap") 30 | private final Map, List>> resolversByType = new HashMap<>(); 31 | private final HierarchyClassResolver hierarchyClassResolver; 32 | 33 | public JsonTypeResolversHierarchyResolver(List> resolvers) { 34 | for (ObjectToJsonTypeConverter resolver : resolvers) { 35 | for (Class typeWhichCanBeResolved : resolver.getClassesWhichCanResolve()) { 36 | List> resolversByClass = resolversByType.get(typeWhichCanBeResolved); 37 | if (resolversByClass == null) { 38 | List> newResolvers = new ArrayList<>(); 39 | newResolvers.add(resolver); 40 | resolversByType.put(typeWhichCanBeResolved, newResolvers); 41 | } else { 42 | resolversByClass.add(resolver); 43 | } 44 | } 45 | } 46 | List> typesWhichCanResolve = new ArrayList<>(); 47 | for (ObjectToJsonTypeConverter resolver : resolvers) { 48 | typesWhichCanResolve.addAll(resolver.getClassesWhichCanResolve()); 49 | } 50 | hierarchyClassResolver = new HierarchyClassResolver(typesWhichCanResolve); 51 | } 52 | 53 | @SuppressWarnings("PMD.CognitiveComplexity") 54 | public AbstractJsonType returnConcreteJsonTypeObject(PrimitiveJsonTypesResolver mainResolver, 55 | Object instance, 56 | String propertyKey) { 57 | Objects.requireNonNull(instance); 58 | Class instanceClass = instance.getClass(); 59 | List> resolvers = resolversByType.get(instanceClass); 60 | if (resolvers == null) { 61 | Class typeWhichCanResolve = hierarchyClassResolver.searchResolverClass(instance); 62 | if (typeWhichCanResolve == null) { 63 | throw new ParsePropertiesException(format(CANNOT_FIND_TYPE_RESOLVER_MSG, instanceClass)); 64 | } 65 | resolvers = resolversByType.get(typeWhichCanResolve); 66 | } 67 | 68 | if (!resolvers.isEmpty()) { 69 | if (instanceClass != String.class && resolvers.size() > 1 70 | && resolvers.stream().anyMatch(resolver -> resolver instanceof PrimitiveJsonTypeResolverToNewApiAdapter)) { 71 | List> resolversClasses = resolvers.stream() 72 | .map(resolver -> { 73 | if (resolver instanceof PrimitiveJsonTypeResolverToNewApiAdapter) { 74 | PrimitiveJsonTypeResolverToNewApiAdapter adapter = (PrimitiveJsonTypeResolverToNewApiAdapter) resolver; 75 | PrimitiveJsonTypeResolver oldImplementation = adapter.getOldImplementation(); 76 | return oldImplementation.getClass(); 77 | } 78 | return resolver.getClass(); 79 | }).collect(toList()); 80 | throw new ParsePropertiesException("Found: " + new ArrayList<>(resolversClasses) + " for type" + instanceClass + " expected only one!"); 81 | } 82 | 83 | for (ObjectToJsonTypeConverter resolver : resolvers) { 84 | Optional abstractJsonType = resolver.returnOptionalJsonType(mainResolver, instance, propertyKey); 85 | if (abstractJsonType.isPresent()) { 86 | return abstractJsonType.get(); 87 | } 88 | } 89 | } 90 | 91 | throw new ParsePropertiesException(format(CANNOT_FIND_JSON_TYPE_OBJ, instanceClass, propertyKey, instance)); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/test/java/pl/jalokim/propertiestojson/resolvers/primitives/example/LocalDateTimeResolverTest.java: -------------------------------------------------------------------------------- 1 | package pl.jalokim.propertiestojson.resolvers.primitives.example; 2 | 3 | 4 | import static java.time.Month.AUGUST; 5 | import static org.assertj.core.api.Assertions.assertThat; 6 | 7 | import com.google.gson.JsonElement; 8 | import com.google.gson.JsonObject; 9 | import com.google.gson.JsonParser; 10 | import java.time.LocalDate; 11 | import java.util.HashMap; 12 | import java.util.Map; 13 | import java.util.Optional; 14 | import org.junit.Test; 15 | import pl.jalokim.propertiestojson.object.AbstractJsonType; 16 | import pl.jalokim.propertiestojson.object.NumberJsonType; 17 | import pl.jalokim.propertiestojson.object.ObjectJsonType; 18 | import pl.jalokim.propertiestojson.util.PropertiesToJsonConverter; 19 | 20 | public class LocalDateTimeResolverTest { 21 | 22 | private static final JsonParser jp = new JsonParser(); 23 | 24 | @Test 25 | public void convertToLocalDateFromText() { 26 | // given 27 | LocalDateTimeResolver resolver = new LocalDateTimeResolver(); 28 | // when 29 | Optional localDate = resolver.returnConcreteValueWhenCanBeResolved(null, "04-08-2019", "some.field"); 30 | // then 31 | assertThat(localDate.isPresent()).isTrue(); 32 | assertThat(localDate.get().getYear()).isEqualTo(2019); 33 | assertThat(localDate.get().getMonth()).isEqualTo(AUGUST); 34 | assertThat(localDate.get().getDayOfMonth()).isEqualTo(4); 35 | } 36 | 37 | @Test 38 | public void convertToLocalDateFromTextForAnotherFormat() { 39 | // given 40 | LocalDateTimeResolver resolver = new LocalDateTimeResolver("dd/MM/yyyy"); 41 | // when 42 | Optional localDate = resolver.returnConcreteValueWhenCanBeResolved(null, "04/08/2019", "some.field"); 43 | // then 44 | assertThat(localDate.isPresent()).isTrue(); 45 | assertThat(localDate.get().getYear()).isEqualTo(2019); 46 | assertThat(localDate.get().getMonth()).isEqualTo(AUGUST); 47 | assertThat(localDate.get().getDayOfMonth()).isEqualTo(4); 48 | } 49 | 50 | @Test 51 | public void notConvertToLocalDateFromTextForAnotherFormat() { 52 | // given 53 | LocalDateTimeResolver resolver = new LocalDateTimeResolver("dd/MM/yyyy"); 54 | // when 55 | Optional localDate = resolver.returnConcreteValueWhenCanBeResolved(null, "m/08/2019", "some.field"); 56 | // then 57 | assertThat(localDate.isPresent()).isFalse(); 58 | } 59 | 60 | @Test 61 | public void convertFromTextDateThroughConverterToObjectJson() { 62 | // given 63 | LocalDateTimeResolver resolver = new LocalDateTimeResolver(); 64 | PropertiesToJsonConverter converter = new PropertiesToJsonConverter(resolver); 65 | Map properties = new HashMap<>(); 66 | properties.put("object.localDateField", "04-08-2019"); 67 | // when 68 | String json = converter.convertToJson(properties); 69 | // then 70 | System.out.println(json); 71 | JsonElement jsonElement = jp.parse(json); 72 | JsonObject asJsonObject = jsonElement.getAsJsonObject().getAsJsonObject("object"); 73 | JsonObject localDateJson = asJsonObject.getAsJsonObject("localDateField"); 74 | assertThat(localDateJson.get("year").getAsInt()).isEqualTo(2019); 75 | assertThat(localDateJson.get("month").getAsInt()).isEqualTo(8); 76 | assertThat(localDateJson.get("day").getAsInt()).isEqualTo(4); 77 | } 78 | 79 | @Test 80 | public void convertFromTextDateThroughConverterToSimpleTimestamp() { 81 | // given 82 | LocalDateTimeResolver resolver = new LocalDateTimeResolver(true); 83 | PropertiesToJsonConverter converter = new PropertiesToJsonConverter(resolver); 84 | Map properties = new HashMap<>(); 85 | properties.put("object.localDateField", "04-08-2019"); 86 | // when 87 | String json = converter.convertToJson(properties); 88 | // then 89 | System.out.println(json); 90 | JsonElement jsonElement = jp.parse(json); 91 | JsonObject asJsonObject = jsonElement.getAsJsonObject().getAsJsonObject("object"); 92 | assertThat(asJsonObject.getAsJsonPrimitive("localDateField").getAsInt()).isEqualTo(1564876800); 93 | } 94 | 95 | @Test 96 | public void convertLocalDateToUtcTimestamp() { 97 | // given 98 | LocalDate localDate = LocalDate.of(2019, 8, 4); 99 | LocalDateTimeResolver resolver = new LocalDateTimeResolver(true); 100 | // when 101 | AbstractJsonType jsonObject = resolver.returnConcreteJsonType(null, localDate, "some.field"); 102 | // then 103 | assertThat(jsonObject).isNotNull(); 104 | NumberJsonType numberJsonType = (NumberJsonType) jsonObject; 105 | assertThat(numberJsonType.toString()).isEqualTo("1564876800"); 106 | } 107 | 108 | @Test 109 | public void convertLocalDateToJsonObject() { 110 | // given 111 | LocalDate localDate = LocalDate.of(2019, 8, 4); 112 | LocalDateTimeResolver resolver = new LocalDateTimeResolver(false); 113 | // when 114 | AbstractJsonType jsonObject = resolver.returnConcreteJsonType(null, localDate, "some.field"); 115 | // then 116 | assertThat(jsonObject).isNotNull(); 117 | ObjectJsonType numberJsonType = (ObjectJsonType) jsonObject; 118 | assertThat(numberJsonType.toString()).isEqualTo("{\"year\":2019,\"month\":8,\"day\":4}"); 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /src/test/java/pl/jalokim/propertiestojson/util/AbstractPropertiesToJsonConverterTest.java: -------------------------------------------------------------------------------- 1 | package pl.jalokim.propertiestojson.util; 2 | 3 | import java.io.FileInputStream; 4 | import java.io.IOException; 5 | import java.io.InputStream; 6 | import java.util.Arrays; 7 | import java.util.HashMap; 8 | import java.util.List; 9 | import java.util.Map; 10 | import java.util.Properties; 11 | import org.assertj.core.api.Assertions; 12 | import pl.jalokim.propertiestojson.domain.MainObject; 13 | 14 | public class AbstractPropertiesToJsonConverterTest { 15 | 16 | protected static final String FIELD2_VALUE = "die3"; 17 | protected static final String FIELD1_VALUE = "die2"; 18 | protected static final String COST_STRING_VALUE = "123.0"; 19 | protected static final Double COST_INT_VALUE = 123.0; 20 | protected static final String INSRANCE_TYPE = "Medical"; 21 | protected static final String STREET = "Jp2"; 22 | protected static final String CITY = "Waraw"; 23 | protected static final String SURNAME = "Surname"; 24 | protected static final String NAME = "John"; 25 | protected static final String EMAIL_1 = "example@gg.com"; 26 | protected static final String EMAIL_2 = "example2@cc.com"; 27 | protected static final String EMAIL_3 = "example3@gg.com"; 28 | protected static final String EMAILS = String.format("%s,%s,%s,%s", EMAIL_1, EMAIL_2, EMAIL_3, EMAIL_3); 29 | protected static final String GROUP_1 = "group1"; 30 | protected static final String COMMERCIAL = "Commercial"; 31 | protected static final String GROUP_3 = "group3"; 32 | protected static final String GROUP_2 = "group2"; 33 | protected static final String FREE = "Free"; 34 | protected static final String CARS = "cars"; 35 | protected static final String COMPUTERS = "computers"; 36 | protected static final String WOMEN = "women"; 37 | protected static final String SCIENCE = "science"; 38 | protected static final String FILE_PATH = "src/test/resources/example.properties"; 39 | protected static final String MAN_COST = "126.543"; 40 | protected static final Double EXPECTED_MAN_COST = 126.543; 41 | 42 | protected InputStream getPropertiesFromFile() throws IOException { 43 | return new FileInputStream(FILE_PATH); 44 | } 45 | 46 | protected Map initProperlyPropertiesMap() { 47 | Map properties = new HashMap<>(); 48 | properties.put("man.name", NAME); 49 | properties.put("man.surname", SURNAME); 50 | properties.put("man.address.city", CITY); 51 | properties.put("man.address.street", STREET); 52 | properties.put("insurance.type", INSRANCE_TYPE); 53 | properties.put("insurance.cost", COST_STRING_VALUE); 54 | properties.put("field1", FIELD1_VALUE); 55 | properties.put("field2", FIELD2_VALUE); 56 | properties.put("man.emails", EMAILS); 57 | properties.put("man.groups[0].name", GROUP_1); 58 | properties.put("man.groups[0].type", COMMERCIAL); 59 | properties.put("man.groups[2].name", GROUP_3); 60 | properties.put("man.groups[2].type", COMMERCIAL); 61 | properties.put("man.groups[1].name", GROUP_2); 62 | properties.put("man.groups[1].type", FREE); 63 | properties.put("man.hoobies[0]", CARS); 64 | properties.put("man.hoobies[3]", COMPUTERS); 65 | properties.put("man.hoobies[2]", WOMEN); 66 | properties.put("man.hoobies[1]", SCIENCE); 67 | properties.put("man.married", "false"); 68 | properties.put("man.insurance.cost", MAN_COST); 69 | properties.put("man.insurance.valid", "true"); 70 | return properties; 71 | } 72 | 73 | protected Properties initProperlyProperties() { 74 | Properties properties = new Properties(); 75 | properties.put("man.name", NAME); 76 | properties.put("man.surname", SURNAME); 77 | properties.put("man.address.city", CITY); 78 | properties.put("man.address.street", STREET); 79 | properties.put("insurance.type", INSRANCE_TYPE); 80 | properties.put("insurance.cost", COST_INT_VALUE); 81 | properties.put("field1", FIELD1_VALUE); 82 | properties.put("field2", FIELD2_VALUE); 83 | properties.put("man.emails", Arrays.asList(EMAIL_1, EMAIL_2, EMAIL_3, EMAIL_3)); 84 | properties.put("man.groups[0].name", GROUP_1); 85 | properties.put("man.groups[0].type", COMMERCIAL); 86 | properties.put("man.groups[2].name", GROUP_3); 87 | properties.put("man.groups[2].type", COMMERCIAL); 88 | properties.put("man.groups[1].name", GROUP_2); 89 | properties.put("man.groups[1].type", FREE); 90 | properties.put("man.hoobies[0]", CARS); 91 | properties.put("man.hoobies[3]", COMPUTERS); 92 | properties.put("man.hoobies[2]", WOMEN); 93 | properties.put("man.hoobies[1]", SCIENCE); 94 | properties.put("man.married", false); 95 | properties.put("man.insurance.cost", EXPECTED_MAN_COST); 96 | properties.put("man.insurance.valid", true); 97 | return properties; 98 | } 99 | 100 | protected void assertGroupByIdAndExpectedValues(MainObject mainObject, int index, String name, String type) { 101 | Assertions.assertThat(mainObject.getMan().getGroups().get(index).getName()).isEqualTo(name); 102 | Assertions.assertThat(mainObject.getMan().getGroups().get(index).getType()).isEqualTo(type); 103 | } 104 | 105 | protected void assertEmailList(MainObject mainObject) { 106 | List emails = mainObject.getMan().getEmails(); 107 | Assertions.assertThat(emails.get(0)).isEqualTo(EMAIL_1); 108 | Assertions.assertThat(emails.get(1)).isEqualTo(EMAIL_2); 109 | Assertions.assertThat(emails.get(2)).isEqualTo(EMAIL_3); 110 | Assertions.assertThat(emails.get(3)).isEqualTo(EMAIL_3); 111 | } 112 | 113 | protected void assertHobbiesList(MainObject mainObject) { 114 | List hobbies = mainObject.getMan().getHoobies(); 115 | Assertions.assertThat(hobbies.get(0)).isEqualTo(CARS); 116 | Assertions.assertThat(hobbies.get(1)).isEqualTo(SCIENCE); 117 | Assertions.assertThat(hobbies.get(2)).isEqualTo(WOMEN); 118 | Assertions.assertThat(hobbies.get(3)).isEqualTo(COMPUTERS); 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /src/main/java/pl/jalokim/propertiestojson/resolvers/hierarchy/HierarchyClassResolver.java: -------------------------------------------------------------------------------- 1 | package pl.jalokim.propertiestojson.resolvers.hierarchy; 2 | 3 | import static pl.jalokim.utils.string.StringUtils.concatElementsAsLines; 4 | 5 | import java.util.ArrayList; 6 | import java.util.Arrays; 7 | import java.util.List; 8 | import java.util.Optional; 9 | import lombok.Data; 10 | import pl.jalokim.propertiestojson.util.exception.ParsePropertiesException; 11 | 12 | public class HierarchyClassResolver { 13 | 14 | private static final String ERROR_MSG = "Found %s resolvers for instance type: %s%nfound resolvers:%n%s"; 15 | 16 | private final List> resolverClasses; 17 | 18 | public HierarchyClassResolver(List> typesWhichCanResolve) { 19 | this.resolverClasses = typesWhichCanResolve; 20 | } 21 | 22 | @SuppressWarnings({"PMD.NPathComplexity", "PMD.CognitiveComplexity"}) 23 | public Class searchResolverClass(Object instance) { 24 | if (instance == null) { 25 | return null; 26 | } 27 | final Class instanceClass = instance.getClass(); 28 | SearchContext searchContext = new SearchContext(); 29 | 30 | for (Class resolverClass : resolverClasses) { 31 | if (resolverClass.isAssignableFrom(instanceClass)) { 32 | if (!resolverClass.isInterface()) { 33 | int difference = countDifferenceForSuperClass(resolverClass, instanceClass); 34 | addToFoundWhenIsClosetsMatch(searchContext, resolverClass, difference); 35 | } 36 | if (resolverClass.isInterface()) { 37 | findDifferenceInSuperClasses(searchContext, resolverClass, instanceClass); 38 | findDifferenceForSuperInterfaces(searchContext, resolverClass, getSuperInterfaces(instanceClass)); 39 | } 40 | } 41 | } 42 | 43 | if (searchContext.getFoundClasses().isEmpty()) { 44 | return null; 45 | } 46 | if (searchContext.getFoundClasses().size() == 1) { 47 | return searchContext.getFoundClasses().get(0); 48 | } 49 | 50 | List> foundClasses = searchContext.getFoundClasses(); 51 | Optional> onlyObjectTypeOpt = foundClasses.stream() 52 | .filter(type -> !type.isInterface() && !type.isEnum()) 53 | .findAny(); 54 | if (onlyObjectTypeOpt.isPresent()) { 55 | if (onlyObjectTypeOpt.get() != Object.class) { 56 | return onlyObjectTypeOpt.get(); 57 | } 58 | List> foundInterfaces = new ArrayList<>(foundClasses); 59 | foundInterfaces.remove(onlyObjectTypeOpt.get()); 60 | 61 | if (foundInterfaces.size() == 1) { 62 | return foundInterfaces.get(0); 63 | } 64 | foundClasses = foundInterfaces; 65 | } 66 | 67 | throw new ParsePropertiesException(String.format(ERROR_MSG, 68 | foundClasses.size(), 69 | instance.getClass().getCanonicalName(), 70 | concatElementsAsLines(foundClasses))); 71 | } 72 | 73 | private void addToFoundWhenIsClosetsMatch(SearchContext searchContext, Class resolverClass, int difference) { 74 | if (difference < searchContext.getCurrentNearCounter()) { 75 | searchContext.setCurrentNearCounter(difference); 76 | searchContext.clear(); 77 | searchContext.add(resolverClass); 78 | } else if (searchContext.getCurrentNearCounter() == difference) { 79 | searchContext.add(resolverClass); 80 | } 81 | } 82 | 83 | private int countDifferenceForSuperClass(Class resolverClass, Class instanceClass) { 84 | Class currentClass = instanceClass; 85 | int counter = 0; 86 | while (!resolverClass.getCanonicalName().equals(currentClass.getCanonicalName())) { 87 | currentClass = currentClass.getSuperclass(); 88 | counter++; 89 | if (currentClass == null) { 90 | return Integer.MAX_VALUE; 91 | } 92 | } 93 | return counter; 94 | } 95 | 96 | private void findDifferenceInSuperClasses(final SearchContext searchContext, final Class resolverClass, final Class instanceClass) { 97 | Class currentClass = instanceClass; 98 | while (currentClass != null) { 99 | searchContext.searchHierarchyLevel++; 100 | currentClass = currentClass.getSuperclass(); 101 | if (currentClass == null) { 102 | break; 103 | } 104 | findDifferenceForSuperInterfaces(searchContext, resolverClass, getSuperInterfaces(currentClass)); 105 | } 106 | searchContext.searchHierarchyLevel = 0; 107 | } 108 | 109 | private void findDifferenceForSuperInterfaces(final SearchContext searchContext, final Class resolverClass, List> interfaceClasses) { 110 | searchContext.searchHierarchyLevel++; 111 | for (Class interfaceClass : interfaceClasses) { 112 | List> superInterfaces = getSuperInterfaces(interfaceClass); 113 | if (!superInterfaces.isEmpty()) { 114 | findDifferenceForSuperInterfaces(searchContext, resolverClass, superInterfaces); 115 | } 116 | if (interfaceClass.getCanonicalName().equals(resolverClass.getCanonicalName())) { 117 | addToFoundWhenIsClosetsMatch(searchContext, resolverClass, searchContext.searchHierarchyLevel); 118 | } 119 | } 120 | searchContext.searchHierarchyLevel--; 121 | } 122 | 123 | private List> getSuperInterfaces(Class type) { 124 | return new ArrayList<>(Arrays.asList(type.getInterfaces())); 125 | } 126 | 127 | @Data 128 | @SuppressWarnings("PMD.RedundantFieldInitializer") 129 | private static class SearchContext { 130 | 131 | final List> foundClasses = new ArrayList<>(); 132 | int currentNearCounter = Integer.MAX_VALUE; 133 | int searchHierarchyLevel = 0; 134 | 135 | public void add(Class type) { 136 | if (!foundClasses.contains(type)) { 137 | foundClasses.add(type); 138 | } 139 | 140 | } 141 | 142 | public void clear() { 143 | foundClasses.clear(); 144 | } 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /src/test/groovy/pl/jalokim/propertiestojson/util/PropertiesToJsonConverterSpockTest.groovy: -------------------------------------------------------------------------------- 1 | package pl.jalokim.propertiestojson.util 2 | 3 | import groovy.json.JsonSlurper 4 | import pl.jalokim.propertiestojson.object.AbstractJsonType 5 | import pl.jalokim.propertiestojson.object.NumberJsonType 6 | import pl.jalokim.propertiestojson.resolvers.PrimitiveJsonTypesResolver 7 | import pl.jalokim.propertiestojson.resolvers.primitives.object.AbstractObjectToJsonTypeConverter 8 | import spock.lang.Specification 9 | 10 | class PropertiesToJsonConverterSpockTest extends Specification { 11 | 12 | def "not split all by dot"() { 13 | given: 14 | 15 | String propertyKey = "field.nextfield.array[0][5].anotherField[external.key.leaf].anotherField[[external.key.leaf][some.strange.things]].fieldLeaf" 16 | def map = [(propertyKey): "someValue"] 17 | when: 18 | def converter = PropertiesToJsonConverterBuilder.builder().build() 19 | def json = converter.convertFromValuesAsObjectMap(map) 20 | def jsonSlurper = new JsonSlurper() 21 | def jsonObject = jsonSlurper.parseText(json) 22 | then: 23 | jsonObject.field.nextfield.array[0][5]["anotherField[external.key.leaf]"]["anotherField[[external.key.leaf][some.strange.things]]"].fieldLeaf == 24 | "someValue" 25 | } 26 | 27 | def "try resolve objects from properties when provided tryConvertStringValuesToOtherObjects=true"() { 28 | given: 29 | def date = new Date(123123) 30 | def properties = new Properties() 31 | properties.put("root.name", "some-name") 32 | properties.put("root.surname", "some-surname") 33 | properties.put("root.someBoolean1", "false") 34 | properties.put("someBoolean2", "true") 35 | properties.put("someNumber", 3.0) 36 | properties.put("someDate", date) 37 | 38 | when: 39 | def converter = PropertiesToJsonConverterBuilder.builder() 40 | .defaultAndCustomObjectToJsonTypeConverters(new DateToTimestamp()) 41 | .build() 42 | 43 | def json = converter.convertToJson(properties, true) 44 | println json 45 | def jsonSlurper = new JsonSlurper() 46 | def jsonObject = jsonSlurper.parseText(json) 47 | 48 | then: 49 | jsonObject.root.name == "some-name" 50 | jsonObject.root.surname == "some-surname" 51 | jsonObject.root.someBoolean1 == false 52 | jsonObject.someBoolean2 == true 53 | jsonObject.someNumber == 3.0 54 | jsonObject.someDate == 123123 55 | } 56 | 57 | def "try resolve objects from properties when provided tryConvertStringValuesToOtherObjects=false"() { 58 | given: 59 | def date = new Date(123123) 60 | def properties = new Properties() 61 | properties.put("root.name", "some-name") 62 | properties.put("root.surname", "some-surname") 63 | properties.put("root.someBoolean1", "false") 64 | properties.put("someBoolean2", "true") 65 | properties.put("someNumber", 3.0) 66 | properties.put("someDate", date) 67 | 68 | when: 69 | def converter = PropertiesToJsonConverterBuilder.builder() 70 | .defaultAndCustomObjectToJsonTypeConverters(new DateToTimestamp()) 71 | .build() 72 | 73 | def json = converter.convertToJson(properties, false) 74 | def jsonSlurper = new JsonSlurper() 75 | def jsonObject = jsonSlurper.parseText(json) 76 | 77 | then: 78 | jsonObject.root.name == "some-name" 79 | jsonObject.root.surname == "some-surname" 80 | jsonObject.root.someBoolean1 == "false" 81 | jsonObject.someBoolean2 == "true" 82 | jsonObject.someNumber == 3.0 83 | jsonObject.someDate == 123123 84 | } 85 | 86 | def "try resolve objects from map when provided tryConvertStringValuesToOtherObjects=true"() { 87 | given: 88 | def date = new Date(123123) 89 | def properties = [ 90 | "root.name" : "some-name", 91 | "root.surname" : "some-surname", 92 | "root.someBoolean1": "false", 93 | "someBoolean2" : "true", 94 | "someNumber" : 3.0, 95 | "someDate" : date, 96 | ] 97 | 98 | when: 99 | def converter = PropertiesToJsonConverterBuilder.builder() 100 | .defaultAndCustomObjectToJsonTypeConverters(new DateToTimestamp()) 101 | .build() 102 | 103 | def json = converter.convertFromValuesAsObjectMap(properties, true) 104 | println json 105 | def jsonSlurper = new JsonSlurper() 106 | def jsonObject = jsonSlurper.parseText(json) 107 | 108 | then: 109 | jsonObject.root.name == "some-name" 110 | jsonObject.root.surname == "some-surname" 111 | jsonObject.root.someBoolean1 == false 112 | jsonObject.someBoolean2 == true 113 | jsonObject.someNumber == 3.0 114 | jsonObject.someDate == 123123 115 | } 116 | 117 | def "try resolve objects from map when provided tryConvertStringValuesToOtherObjects=false"() { 118 | given: 119 | def date = new Date(123123) 120 | def properties = [ 121 | "root.name" : "some-name", 122 | "root.surname" : "some-surname", 123 | "root.someBoolean1": "false", 124 | "someBoolean2" : "true", 125 | "someNumber" : 3.0, 126 | "someDate" : date, 127 | ] 128 | 129 | when: 130 | def converter = PropertiesToJsonConverterBuilder.builder() 131 | .defaultAndCustomObjectToJsonTypeConverters(new DateToTimestamp()) 132 | .build() 133 | 134 | def json = converter.convertFromValuesAsObjectMap(properties, false) 135 | def jsonSlurper = new JsonSlurper() 136 | def jsonObject = jsonSlurper.parseText(json) 137 | 138 | then: 139 | jsonObject.root.name == "some-name" 140 | jsonObject.root.surname == "some-surname" 141 | jsonObject.root.someBoolean1 == "false" 142 | jsonObject.someBoolean2 == "true" 143 | jsonObject.someNumber == 3.0 144 | jsonObject.someDate == 123123 145 | } 146 | 147 | private static class DateToTimestamp extends AbstractObjectToJsonTypeConverter { 148 | 149 | @Override 150 | Optional convertToJsonTypeOrEmpty(PrimitiveJsonTypesResolver primitiveJsonTypesResolver, 151 | Date convertedValue, String propertyKey) { 152 | Optional.of(new NumberJsonType(convertedValue.getTime())) 153 | } 154 | } 155 | 156 | } 157 | -------------------------------------------------------------------------------- /src/main/java/pl/jalokim/propertiestojson/resolvers/PrimitiveJsonTypesResolver.java: -------------------------------------------------------------------------------- 1 | package pl.jalokim.propertiestojson.resolvers; 2 | 3 | import static pl.jalokim.propertiestojson.JsonObjectFieldsValidator.isArrayJson; 4 | import static pl.jalokim.propertiestojson.object.JsonNullReferenceType.NULL_OBJECT; 5 | 6 | import com.google.common.collect.ImmutableList; 7 | import java.util.List; 8 | import java.util.Optional; 9 | import pl.jalokim.propertiestojson.JsonObjectFieldsValidator; 10 | import pl.jalokim.propertiestojson.object.AbstractJsonType; 11 | import pl.jalokim.propertiestojson.object.ArrayJsonType; 12 | import pl.jalokim.propertiestojson.object.JsonNullReferenceType; 13 | import pl.jalokim.propertiestojson.object.ObjectJsonType; 14 | import pl.jalokim.propertiestojson.object.SkipJsonField; 15 | import pl.jalokim.propertiestojson.path.PathMetadata; 16 | import pl.jalokim.propertiestojson.resolvers.hierarchy.JsonTypeResolversHierarchyResolver; 17 | import pl.jalokim.propertiestojson.resolvers.primitives.object.NullToJsonTypeConverter; 18 | import pl.jalokim.propertiestojson.resolvers.primitives.object.ObjectToJsonTypeConverter; 19 | import pl.jalokim.propertiestojson.resolvers.primitives.string.TextToConcreteObjectResolver; 20 | import pl.jalokim.propertiestojson.util.exception.CannotOverrideFieldException; 21 | 22 | public class PrimitiveJsonTypesResolver extends JsonTypeResolver { 23 | 24 | private final List> toObjectsResolvers; 25 | private final JsonTypeResolversHierarchyResolver resolversHierarchyResolver; 26 | private final Boolean skipNulls; 27 | private final NullToJsonTypeConverter nullToJsonTypeConverter; 28 | 29 | public PrimitiveJsonTypesResolver(List> toObjectsResolvers, 30 | List> toJsonResolvers, 31 | Boolean skipNulls, 32 | NullToJsonTypeConverter nullToJsonTypeConverter) { 33 | this.toObjectsResolvers = ImmutableList.copyOf(toObjectsResolvers); 34 | this.resolversHierarchyResolver = new JsonTypeResolversHierarchyResolver(toJsonResolvers); 35 | this.skipNulls = skipNulls; 36 | this.nullToJsonTypeConverter = nullToJsonTypeConverter; 37 | } 38 | 39 | @Override 40 | public ObjectJsonType traverse(PathMetadata currentPathMetaData) { 41 | addPrimitiveFieldWhenIsValid(currentPathMetaData); 42 | return null; 43 | } 44 | 45 | private void addPrimitiveFieldWhenIsValid(PathMetadata currentPathMetaData) { 46 | JsonObjectFieldsValidator.checkThatFieldCanBeSet(currentObjectJsonType, currentPathMetaData, propertyKey); 47 | addPrimitiveFieldToCurrentJsonObject(currentPathMetaData); 48 | } 49 | 50 | private void addPrimitiveFieldToCurrentJsonObject(PathMetadata currentPathMetaData) { 51 | String field = currentPathMetaData.getFieldName(); 52 | if (currentPathMetaData.isArrayField()) { 53 | addFieldToArray(currentPathMetaData); 54 | } else { 55 | if (currentObjectJsonType.containsField(field) && isArrayJson(currentObjectJsonType.getField(field))) { 56 | AbstractJsonType abstractJsonType = currentPathMetaData.getJsonValue(); 57 | ArrayJsonType currentArrayInObject = currentObjectJsonType.getJsonArray(field); 58 | if (isArrayJson(abstractJsonType)) { 59 | ArrayJsonType newArray = (ArrayJsonType) abstractJsonType; 60 | List abstractJsonTypes = newArray.convertToListWithoutRealNull(); 61 | for (int i = 0; i < abstractJsonTypes.size(); i++) { 62 | currentArrayInObject.addElement(i, abstractJsonTypes.get(i), currentPathMetaData); 63 | } 64 | } else { 65 | throw new CannotOverrideFieldException(currentPathMetaData.getCurrentFullPath(), currentArrayInObject, propertyKey); 66 | } 67 | } else { 68 | currentObjectJsonType.addField(field, currentPathMetaData.getJsonValue(), currentPathMetaData); 69 | } 70 | } 71 | } 72 | 73 | public Object getResolvedObject(String propertyValue, String propertyKey) { 74 | Optional objectOptional = Optional.empty(); 75 | for (TextToConcreteObjectResolver primitiveResolver : toObjectsResolvers) { 76 | if (!objectOptional.isPresent()) { 77 | objectOptional = primitiveResolver.returnConvertedValueForClearedText(this, propertyValue, propertyKey); 78 | } 79 | } 80 | return objectOptional.orElse(null); 81 | } 82 | 83 | public AbstractJsonType resolvePrimitiveTypeAndReturn(Object propertyValue, String propertyKey) { 84 | AbstractJsonType result; 85 | if (propertyValue == null) { 86 | result = nullToJsonTypeConverter.convertToJsonTypeOrEmpty(this, NULL_OBJECT, propertyKey).get(); 87 | } else { 88 | result = resolversHierarchyResolver.returnConcreteJsonTypeObject(this, propertyValue, propertyKey); 89 | } 90 | 91 | if (Boolean.TRUE.equals(skipNulls) && result instanceof JsonNullReferenceType) { 92 | result = SkipJsonField.SKIP_JSON_FIELD; 93 | } 94 | 95 | return result; 96 | } 97 | 98 | protected void addFieldToArray(PathMetadata currentPathMetaData) { 99 | if (arrayWithGivenFieldNameExist(currentPathMetaData.getFieldName())) { 100 | fetchArrayAndAddElement(currentPathMetaData); 101 | } else { 102 | createArrayAndAddElement(currentPathMetaData); 103 | } 104 | } 105 | 106 | private boolean arrayWithGivenFieldNameExist(String field) { 107 | return currentObjectJsonType.containsField(field); 108 | } 109 | 110 | private void createArrayAndAddElement(PathMetadata currentPathMetaData) { 111 | ArrayJsonType arrayJsonTypeObject = new ArrayJsonType(); 112 | addElementToArray(currentPathMetaData, arrayJsonTypeObject); 113 | currentObjectJsonType.addField(currentPathMetaData.getFieldName(), arrayJsonTypeObject, currentPathMetaData); 114 | } 115 | 116 | private void fetchArrayAndAddElement(PathMetadata currentPathMetaData) { 117 | ArrayJsonType arrayJsonType = getArrayJsonWhenIsValid(currentPathMetaData); 118 | addElementToArray(currentPathMetaData, arrayJsonType); 119 | } 120 | 121 | private void addElementToArray(PathMetadata currentPathMetaData, ArrayJsonType arrayJsonTypeObject) { 122 | arrayJsonTypeObject.addElement(currentPathMetaData.getPropertyArrayHelper(), currentPathMetaData.getJsonValue(), currentPathMetaData); 123 | } 124 | 125 | } 126 | -------------------------------------------------------------------------------- /src/main/java/pl/jalokim/propertiestojson/resolvers/primitives/utils/JsonObjectHelper.java: -------------------------------------------------------------------------------- 1 | package pl.jalokim.propertiestojson.resolvers.primitives.utils; 2 | 3 | import static pl.jalokim.propertiestojson.Constants.ARRAY_END_SIGN; 4 | import static pl.jalokim.propertiestojson.Constants.ARRAY_START_SIGN; 5 | import static pl.jalokim.propertiestojson.Constants.JSON_OBJECT_END; 6 | import static pl.jalokim.propertiestojson.Constants.JSON_OBJECT_START; 7 | import static pl.jalokim.propertiestojson.object.JsonNullReferenceType.NULL_OBJECT; 8 | import static pl.jalokim.propertiestojson.resolvers.primitives.object.NullToJsonTypeConverter.NULL_TO_JSON_RESOLVER; 9 | import static pl.jalokim.propertiestojson.resolvers.primitives.object.StringToJsonTypeConverter.STRING_TO_JSON_RESOLVER; 10 | import static pl.jalokim.propertiestojson.resolvers.primitives.string.TextToNumberResolver.convertToNumber; 11 | import static pl.jalokim.propertiestojson.resolvers.primitives.string.TextToStringResolver.TO_STRING_RESOLVER; 12 | 13 | import com.google.gson.Gson; 14 | import com.google.gson.JsonArray; 15 | import com.google.gson.JsonElement; 16 | import com.google.gson.JsonObject; 17 | import com.google.gson.JsonParser; 18 | import com.google.gson.JsonPrimitive; 19 | import java.util.ArrayList; 20 | import java.util.List; 21 | import java.util.Map; 22 | import pl.jalokim.propertiestojson.object.AbstractJsonType; 23 | import pl.jalokim.propertiestojson.object.ArrayJsonType; 24 | import pl.jalokim.propertiestojson.object.ObjectJsonType; 25 | import pl.jalokim.propertiestojson.resolvers.PrimitiveJsonTypesResolver; 26 | import pl.jalokim.propertiestojson.resolvers.primitives.object.BooleanToJsonTypeConverter; 27 | import pl.jalokim.propertiestojson.resolvers.primitives.object.NumberToJsonTypeConverter; 28 | import pl.jalokim.propertiestojson.resolvers.primitives.object.ObjectToJsonTypeConverter; 29 | import pl.jalokim.propertiestojson.resolvers.primitives.string.TextToBooleanResolver; 30 | import pl.jalokim.propertiestojson.resolvers.primitives.string.TextToConcreteObjectResolver; 31 | import pl.jalokim.propertiestojson.resolvers.primitives.string.TextToNumberResolver; 32 | 33 | public final class JsonObjectHelper { 34 | 35 | private static final PrimitiveJsonTypesResolver PRIMITIVE_JSON_TYPES_RESOLVER; 36 | private static final JsonParser JSON_PARSER = new JsonParser(); 37 | private static final Gson GSON = new Gson(); 38 | 39 | static { 40 | List> toJsonResolvers = new ArrayList<>(); 41 | toJsonResolvers.add(new NumberToJsonTypeConverter()); 42 | toJsonResolvers.add(new BooleanToJsonTypeConverter()); 43 | toJsonResolvers.add(STRING_TO_JSON_RESOLVER); 44 | 45 | List> toObjectsResolvers = new ArrayList<>(); 46 | toObjectsResolvers.add(new TextToNumberResolver()); 47 | toObjectsResolvers.add(new TextToBooleanResolver()); 48 | toObjectsResolvers.add(TO_STRING_RESOLVER); 49 | PRIMITIVE_JSON_TYPES_RESOLVER = new PrimitiveJsonTypesResolver(toObjectsResolvers, toJsonResolvers, false, NULL_TO_JSON_RESOLVER); 50 | } 51 | 52 | private JsonObjectHelper() { 53 | } 54 | 55 | public static String toJson(Object object) { 56 | return GSON.toJson(object); 57 | } 58 | 59 | public static JsonElement toJsonElement(String json) { 60 | return JSON_PARSER.parse(json); 61 | } 62 | 63 | public static ObjectJsonType createObjectJsonType(JsonElement parsedJson, String propertyKey) { 64 | ObjectJsonType objectJsonType = new ObjectJsonType(); 65 | JsonObject asJsonObject = parsedJson.getAsJsonObject(); 66 | for (Map.Entry entry : asJsonObject.entrySet()) { 67 | JsonElement someField = entry.getValue(); 68 | AbstractJsonType valueOfNextField = convertToAbstractJsonType(someField, propertyKey); 69 | objectJsonType.addField(entry.getKey(), valueOfNextField, null); 70 | } 71 | return objectJsonType; 72 | } 73 | 74 | public static AbstractJsonType convertToAbstractJsonType(JsonElement someField, String propertyKey) { 75 | AbstractJsonType valueOfNextField = null; 76 | if (someField.isJsonNull()) { 77 | valueOfNextField = NULL_OBJECT; 78 | } 79 | if (someField.isJsonObject()) { 80 | valueOfNextField = createObjectJsonType(someField, propertyKey); 81 | } 82 | if (someField.isJsonArray()) { 83 | valueOfNextField = createArrayJsonType(someField, propertyKey); 84 | } 85 | if (someField.isJsonPrimitive()) { 86 | JsonPrimitive jsonPrimitive = someField.getAsJsonPrimitive(); 87 | if (jsonPrimitive.isString()) { 88 | valueOfNextField = PRIMITIVE_JSON_TYPES_RESOLVER.resolvePrimitiveTypeAndReturn(jsonPrimitive.getAsString(), propertyKey); 89 | } else if (jsonPrimitive.isNumber()) { 90 | String numberAsText = jsonPrimitive.getAsNumber().toString(); 91 | valueOfNextField = PRIMITIVE_JSON_TYPES_RESOLVER.resolvePrimitiveTypeAndReturn(convertToNumber(numberAsText), propertyKey); 92 | } else if (jsonPrimitive.isBoolean()) { 93 | valueOfNextField = PRIMITIVE_JSON_TYPES_RESOLVER.resolvePrimitiveTypeAndReturn(jsonPrimitive.getAsBoolean(), propertyKey); 94 | } 95 | } 96 | return valueOfNextField; 97 | } 98 | 99 | public static ArrayJsonType createArrayJsonType(JsonElement parsedJson, String propertyKey) { 100 | ArrayJsonType arrayJsonType = new ArrayJsonType(); 101 | JsonArray asJsonArray = parsedJson.getAsJsonArray(); 102 | int index = 0; 103 | for (JsonElement element : asJsonArray) { 104 | arrayJsonType.addElement(index, convertToAbstractJsonType(element, propertyKey), null); 105 | index++; 106 | } 107 | return arrayJsonType; 108 | } 109 | 110 | public static boolean isValidJsonObjectOrArray(String propertyValue) { 111 | if (hasJsonObjectSignature(propertyValue) || hasJsonArraySignature(propertyValue)) { 112 | JsonParser jp = new JsonParser(); 113 | try { 114 | jp.parse(propertyValue); 115 | return true; 116 | } catch (Exception ex) { 117 | return false; 118 | } 119 | } 120 | return false; 121 | } 122 | 123 | public static boolean hasJsonArraySignature(String propertyValue) { 124 | return hasJsonSignature(propertyValue.trim(), ARRAY_START_SIGN, ARRAY_END_SIGN); 125 | } 126 | 127 | public static boolean hasJsonObjectSignature(String propertyValue) { 128 | return hasJsonSignature(propertyValue.trim(), JSON_OBJECT_START, JSON_OBJECT_END); 129 | } 130 | 131 | private static boolean hasJsonSignature(String propertyValue, String startSign, String endSign) { 132 | return firsLetter(propertyValue).contains(startSign) && lastLetter(propertyValue).contains(endSign); 133 | } 134 | 135 | private static String firsLetter(String text) { 136 | return text.substring(0, 1); 137 | } 138 | 139 | private static String lastLetter(String text) { 140 | return text.substring(text.length() - 1); 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /src/test/java/pl/jalokim/propertiestojson/util/PropertiesToJsonConverterTest.java: -------------------------------------------------------------------------------- 1 | package pl.jalokim.propertiestojson.util; 2 | 3 | import static org.assertj.core.api.Assertions.assertThat; 4 | import static org.junit.Assert.fail; 5 | 6 | import com.google.gson.Gson; 7 | import com.google.gson.JsonSyntaxException; 8 | import java.io.File; 9 | import java.io.InputStream; 10 | import java.util.Optional; 11 | import java.util.Properties; 12 | import org.junit.Test; 13 | import pl.jalokim.propertiestojson.domain.MainComplexObject; 14 | import pl.jalokim.propertiestojson.domain.MainObject; 15 | import pl.jalokim.propertiestojson.object.AbstractJsonType; 16 | import pl.jalokim.propertiestojson.resolvers.PrimitiveJsonTypesResolver; 17 | import pl.jalokim.propertiestojson.resolvers.primitives.object.AbstractObjectToJsonTypeConverter; 18 | 19 | 20 | public class PropertiesToJsonConverterTest extends AbstractPropertiesToJsonConverterTest { 21 | 22 | @Test 23 | public void returnExpectedJsonFromGivenFile() { 24 | //when 25 | String json = new PropertiesToJsonConverter().convertPropertiesFromFileToJson(new File("src/test/resources/primitiveTypes.properties")); 26 | // then 27 | assertJsonWithPrimitivesTypes(json); 28 | } 29 | 30 | @Test 31 | public void returnExpectedJsonWithGivenIncludeFromGivenFile() { 32 | //when 33 | String json = new PropertiesToJsonConverter() 34 | .convertPropertiesFromFileToJson(new File("src/test/resources/primitiveTypes.properties"), "complexObject"); 35 | // then 36 | assertJsonWithPrimitivesTypesWithoutSimpleText(json); 37 | } 38 | 39 | @Test 40 | public void returnExpectedJsonFromGivenFilePath() { 41 | //when 42 | String json = new PropertiesToJsonConverter().convertPropertiesFromFileToJson("src/test/resources/primitiveTypes.properties"); 43 | // then 44 | assertJsonWithPrimitivesTypes(json); 45 | } 46 | 47 | @Test 48 | public void returnExpectedJsonWithGivenIncludeFromGivenFilePath() { 49 | //when 50 | String json = new PropertiesToJsonConverter().convertPropertiesFromFileToJson("src/test/resources/primitiveTypes.properties", "complexObject"); 51 | // then 52 | assertJsonWithPrimitivesTypesWithoutSimpleText(json); 53 | } 54 | 55 | @Test 56 | public void returnExpectedJsonGivenFromInputStream() throws Exception { 57 | // given 58 | InputStream inputStream = getPropertiesFromFile(); 59 | // when 60 | String json = new PropertiesToJsonConverter().convertToJson(inputStream); 61 | // then 62 | assertJsonIsAsExpected(json); 63 | } 64 | 65 | @Test 66 | public void returnExpectedJsonGivenByMap() { 67 | //when 68 | //given 69 | String json = new PropertiesToJsonConverter().convertToJson(initProperlyPropertiesMap()); 70 | //then 71 | assertJsonIsAsExpected(json); 72 | } 73 | 74 | 75 | @Test 76 | public void cannotCreateJsonWhenIsNotFormattedCorrectly() { 77 | // given 78 | PropertiesToJsonConverter converter = PropertiesToJsonConverterBuilder.builder() 79 | .defaultAndCustomObjectToJsonTypeConverters(new InvalidConverter()) 80 | .build(); 81 | Properties properties = new Properties(); 82 | properties.put("some.given.path", "someText"); 83 | // when 84 | try { 85 | String json = converter.convertToJson(properties); 86 | System.out.println(json); 87 | fail(); 88 | } catch (JsonSyntaxException ex) { 89 | // then 90 | assertThat(ex.getMessage()) 91 | .isEqualTo("com.google.gson.stream.MalformedJsonException: Expected ':' at line 1 column 36 path $.some.given.path.someText"); 92 | } 93 | } 94 | 95 | @Test 96 | public void returnExpectedJsonGivenByProperties() { 97 | //when 98 | //given 99 | String json = new PropertiesToJsonConverter().convertToJson(initProperlyProperties()); 100 | //then 101 | assertJsonIsAsExpected(json); 102 | } 103 | 104 | @Test 105 | public void jsonWithExpectedOrderOfProperties() { 106 | // when 107 | String json = new PropertiesToJsonConverter().convertPropertiesFromFileToJson("src/test/resources/order-of-properties.properties"); 108 | // then 109 | assertThat(json).isEqualTo("{\n" + 110 | " \"someField\": {\n" + 111 | " \"nextField0\": {\n" + 112 | " \"leaf1\": 1,\n" + 113 | " \"leaf2\": 2,\n" + 114 | " \"leaf3\": 3\n" + 115 | " },\n" + 116 | " \"nextField1\": {\n" + 117 | " \"leaf1\": 1\n" + 118 | " }\n" + 119 | " },\n" + 120 | " \"anotherField\": {\n" + 121 | " \"nextField\": {\n" + 122 | " \"leaf1\": 1,\n" + 123 | " \"leaf2\": 2\n" + 124 | " }\n" + 125 | " },\n" + 126 | " \"0field\": 0\n" + 127 | "}"); 128 | } 129 | 130 | @Test 131 | public void jsonWithExpectedOrderOfPropertiesDuringFiltering() { 132 | // when 133 | String json = new PropertiesToJsonConverter() 134 | .convertPropertiesFromFileToJson("src/test/resources/order-of-properties.properties", "someField.nextField0", "0field"); 135 | // then 136 | assertThat(json).isEqualTo("{\n" + 137 | " \"someField\": {\n" + 138 | " \"nextField0\": {\n" + 139 | " \"leaf1\": 1,\n" + 140 | " \"leaf2\": 2,\n" + 141 | " \"leaf3\": 3\n" + 142 | " }\n" + 143 | " },\n" + 144 | " \"0field\": 0\n" + 145 | "}"); 146 | } 147 | 148 | private void assertJsonWithPrimitivesTypesWithoutSimpleText(String json) { 149 | Gson gson = new Gson(); 150 | MainComplexObject mainComplexObject = gson.fromJson(json, MainComplexObject.class); 151 | assertThat(mainComplexObject.getComplexObject().getBooleans().getTrueValue()).isTrue(); 152 | assertThat(mainComplexObject.getComplexObject().getBooleans().getFalseValue()).isFalse(); 153 | assertThat(mainComplexObject.getComplexObject().getNumbers().getDoubleValue()).isEqualTo(11.0d); 154 | assertThat(mainComplexObject.getComplexObject().getNumbers().getIntegerValue()).isEqualTo(11); 155 | assertThat(mainComplexObject.getComplexObject().getText()).isEqualTo("text"); 156 | } 157 | 158 | private void assertJsonWithPrimitivesTypes(String json) { 159 | assertJsonWithPrimitivesTypesWithoutSimpleText(json); 160 | Gson gson = new Gson(); 161 | MainComplexObject mainComplexObject = gson.fromJson(json, MainComplexObject.class); 162 | assertThat(mainComplexObject.getSimpleText()).isEqualTo("text2"); 163 | } 164 | 165 | private void assertJsonIsAsExpected(String json) { 166 | Gson gson = new Gson(); 167 | MainObject mainObject = gson.fromJson(json, MainObject.class); 168 | assertThat(mainObject.getField1()).isEqualTo(FIELD1_VALUE); 169 | assertThat(mainObject.getField2()).isEqualTo(FIELD2_VALUE); 170 | assertThat(mainObject.getInsurance().getCost()).isEqualTo(COST_INT_VALUE); 171 | assertThat(mainObject.getInsurance().getType()).isEqualTo(INSRANCE_TYPE); 172 | assertThat(mainObject.getMan().getAddress().getCity()).isEqualTo(CITY); 173 | assertThat(mainObject.getMan().getAddress().getStreet()).isEqualTo(STREET); 174 | assertThat(mainObject.getMan().getName()).isEqualTo(NAME); 175 | assertThat(mainObject.getMan().getSurname()).isEqualTo(SURNAME); 176 | assertThat(mainObject.getMan().getMarried()).isEqualTo(false); 177 | assertThat(mainObject.getMan().getInsurance().getCost()).isEqualTo(EXPECTED_MAN_COST); 178 | assertThat(mainObject.getMan().getInsurance().getValid()).isEqualTo(true); 179 | assertEmailList(mainObject); 180 | assertGroupByIdAndExpectedValues(mainObject, 0, GROUP_1, COMMERCIAL); 181 | assertGroupByIdAndExpectedValues(mainObject, 1, GROUP_2, FREE); 182 | assertGroupByIdAndExpectedValues(mainObject, 2, GROUP_3, COMMERCIAL); 183 | assertHobbiesList(mainObject); 184 | } 185 | 186 | private static class InvalidConverter extends AbstractObjectToJsonTypeConverter { 187 | 188 | @Override 189 | public Optional convertToJsonTypeOrEmpty(PrimitiveJsonTypesResolver primitiveJsonTypesResolver, 190 | String convertedValue, 191 | String propertyKey) { 192 | return Optional.of(new OwnAbstractJsonType(convertedValue)); 193 | } 194 | } 195 | 196 | private static class OwnAbstractJsonType extends AbstractJsonType { 197 | 198 | private String value; 199 | 200 | OwnAbstractJsonType(String value) { 201 | this.value = value; 202 | } 203 | 204 | @Override 205 | public String toStringJson() { 206 | return "{" + value + "}"; 207 | } 208 | } 209 | 210 | } 211 | 212 | -------------------------------------------------------------------------------- /src/main/java/pl/jalokim/propertiestojson/object/ArrayJsonType.java: -------------------------------------------------------------------------------- 1 | package pl.jalokim.propertiestojson.object; 2 | 3 | import static pl.jalokim.propertiestojson.Constants.ARRAY_END_SIGN; 4 | import static pl.jalokim.propertiestojson.Constants.ARRAY_START_SIGN; 5 | import static pl.jalokim.propertiestojson.Constants.EMPTY_STRING; 6 | import static pl.jalokim.propertiestojson.Constants.NEW_LINE_SIGN; 7 | import static pl.jalokim.propertiestojson.object.JsonNullReferenceType.NULL_OBJECT; 8 | import static pl.jalokim.propertiestojson.object.MergableObject.mergeObjectIfPossible; 9 | import static pl.jalokim.utils.collection.CollectionUtils.getLastIndex; 10 | import static pl.jalokim.utils.collection.CollectionUtils.isLastIndex; 11 | 12 | import java.util.ArrayList; 13 | import java.util.Collection; 14 | import java.util.Iterator; 15 | import java.util.List; 16 | import pl.jalokim.propertiestojson.PropertyArrayHelper; 17 | import pl.jalokim.propertiestojson.path.PathMetadata; 18 | import pl.jalokim.propertiestojson.resolvers.PrimitiveJsonTypesResolver; 19 | import pl.jalokim.propertiestojson.util.exception.CannotOverrideFieldException; 20 | 21 | public class ArrayJsonType extends AbstractJsonType implements MergableObject { 22 | 23 | public static final int INIT_SIZE = 100; 24 | private AbstractJsonType[] elements = new AbstractJsonType[INIT_SIZE]; 25 | private int maxIndex = -1; 26 | 27 | public ArrayJsonType() { 28 | } 29 | 30 | @SuppressWarnings("PMD.ConstructorCallsOverridableMethod") 31 | public ArrayJsonType(PrimitiveJsonTypesResolver primitiveJsonTypesResolver, Collection elements, PathMetadata currentPathMetadata, String propertyKey) { 32 | Iterator iterator = elements.iterator(); 33 | int index = 0; 34 | while (iterator.hasNext()) { 35 | Object element = iterator.next(); 36 | addElement(index, primitiveJsonTypesResolver.resolvePrimitiveTypeAndReturn(element, propertyKey), currentPathMetadata); 37 | index++; 38 | } 39 | } 40 | 41 | public static ArrayJsonType createOrGetNextDimensionOfArray(ArrayJsonType currentArray, List indexes, int indexToTest, 42 | PathMetadata currentPathMetadata) { 43 | if (currentArray.existElementByGivenIndex(indexes.get(indexToTest))) { 44 | AbstractJsonType element = currentArray.getElement(indexes.get(indexToTest)); 45 | if (element instanceof ArrayJsonType) { 46 | return (ArrayJsonType) element; 47 | } else { 48 | List currentIndexes = indexes.subList(0, indexToTest + 1); 49 | String indexesAsText = currentIndexes.stream() 50 | .map(Object::toString) 51 | .reduce(EMPTY_STRING, (oldText, index) -> oldText + ARRAY_START_SIGN + index + ARRAY_END_SIGN); 52 | throw new CannotOverrideFieldException(currentPathMetadata.getCurrentFullPathWithoutIndexes() + indexesAsText, element, 53 | currentPathMetadata.getOriginalPropertyKey()); 54 | } 55 | } else { 56 | ArrayJsonType newArray = new ArrayJsonType(); 57 | currentArray.addElement(indexes.get(indexToTest), newArray, currentPathMetadata); 58 | return newArray; 59 | } 60 | } 61 | 62 | public void addElement(int index, AbstractJsonType elementToAdd, PathMetadata currentPathMetadata) { 63 | if (maxIndex < index) { 64 | maxIndex = index; 65 | } 66 | rewriteArrayWhenIsFull(index); 67 | AbstractJsonType oldObject = elements[index]; 68 | 69 | if (oldObject == null) { 70 | elements[index] = elementToAdd; 71 | } else { 72 | if (oldObject instanceof MergableObject && elementToAdd instanceof MergableObject) { 73 | mergeObjectIfPossible(oldObject, elementToAdd, currentPathMetadata); 74 | } else { 75 | throw new CannotOverrideFieldException(currentPathMetadata.getCurrentFullPath(), oldObject, currentPathMetadata.getOriginalPropertyKey()); 76 | } 77 | } 78 | } 79 | 80 | public void addElement(PropertyArrayHelper propertyArrayHelper, AbstractJsonType elementToAdd, PathMetadata currentPathMetadata) { 81 | List indexes = propertyArrayHelper.getDimensionalIndexes(); 82 | int size = propertyArrayHelper.getDimensionalIndexes().size(); 83 | ArrayJsonType currentArray = this; 84 | for (int index = 0; index < size; index++) { 85 | if (isLastIndex(propertyArrayHelper.getDimensionalIndexes(), index)) { 86 | currentArray.addElement(indexes.get(index), elementToAdd, currentPathMetadata); 87 | } else { 88 | currentArray = createOrGetNextDimensionOfArray(currentArray, indexes, index, currentPathMetadata); 89 | } 90 | } 91 | } 92 | 93 | public AbstractJsonType getElementByGivenDimIndexes(PathMetadata currentPathMetaData) { 94 | PropertyArrayHelper propertyArrayHelper = currentPathMetaData.getPropertyArrayHelper(); 95 | List indexes = propertyArrayHelper.getDimensionalIndexes(); 96 | int size = propertyArrayHelper.getDimensionalIndexes().size(); 97 | ArrayJsonType currentArray = this; 98 | for (int i = 0; i < size; i++) { 99 | if (isLastIndex(propertyArrayHelper.getDimensionalIndexes(), i)) { 100 | return currentArray.getElement(indexes.get(i)); 101 | } else { 102 | AbstractJsonType element = currentArray.getElement(indexes.get(i)); 103 | if (element == null) { 104 | return null; 105 | } 106 | if (element instanceof ArrayJsonType) { 107 | currentArray = (ArrayJsonType) element; 108 | } else { 109 | List currentIndexes = indexes.subList(0, i + 1); 110 | String indexesAsText = currentIndexes.stream() 111 | .map(Object::toString) 112 | .reduce(EMPTY_STRING, (oldText, index) -> oldText + ARRAY_START_SIGN + index + ARRAY_END_SIGN); 113 | throw new CannotOverrideFieldException(currentPathMetaData.getCurrentFullPathWithoutIndexes() + indexesAsText, element, 114 | currentPathMetaData.getOriginalPropertyKey()); 115 | } 116 | } 117 | } 118 | throw new UnsupportedOperationException( 119 | "cannot return expected object for " + currentPathMetaData.getCurrentFullPath() + " " + currentPathMetaData.getPropertyArrayHelper() 120 | .getDimensionalIndexes()); 121 | } 122 | 123 | public boolean existElementByGivenIndex(int index) { 124 | return getElement(index) != null; 125 | } 126 | 127 | private void rewriteArrayWhenIsFull(int index) { 128 | if (indexHigherThanArraySize(index)) { 129 | int predictedNewSize = elements.length + INIT_SIZE; 130 | int newSize = predictedNewSize > index ? predictedNewSize : index + 1; 131 | AbstractJsonType[] elementsTemp = new AbstractJsonType[newSize]; 132 | System.arraycopy(elements, 0, elementsTemp, 0, elements.length); 133 | elements = elementsTemp; 134 | } 135 | } 136 | 137 | private boolean indexHigherThanArraySize(int index) { 138 | return index > getLastIndex(elements); 139 | } 140 | 141 | public AbstractJsonType getElement(int index) { 142 | rewriteArrayWhenIsFull(index); 143 | return elements[index]; 144 | } 145 | 146 | @Override 147 | public String toStringJson() { 148 | StringBuilder result = new StringBuilder().append(ARRAY_START_SIGN); 149 | int index = 0; 150 | List elementsAsList = convertToListWithoutRealNull(); 151 | int lastIndex = getLastIndex(elementsAsList); 152 | for (AbstractJsonType element : elementsAsList) { 153 | if (!(element instanceof SkipJsonField)) { 154 | String lastSign = index == lastIndex ? EMPTY_STRING : NEW_LINE_SIGN; 155 | result.append(element.toStringJson()) 156 | .append(lastSign); 157 | } 158 | index++; 159 | } 160 | return result.append(ARRAY_END_SIGN).toString(); 161 | } 162 | 163 | public List convertToListWithoutRealNull() { 164 | List elementsList = new ArrayList<>(); 165 | 166 | for (int i = 0; i < maxIndex + 1; i++) { 167 | AbstractJsonType element = elements[i]; 168 | if (element == null) { 169 | elementsList.add(NULL_OBJECT); 170 | } else { 171 | elementsList.add(element); 172 | } 173 | } 174 | return elementsList; 175 | } 176 | 177 | private List convertToListWithRealNull() { 178 | List elementsList = new ArrayList<>(); 179 | for (int i = 0; i < maxIndex + 1; i++) { 180 | AbstractJsonType element = elements[i]; 181 | elementsList.add(element); 182 | } 183 | return elementsList; 184 | } 185 | 186 | @Override 187 | public void merge(ArrayJsonType mergeWith, PathMetadata currentPathMetadata) { 188 | int index = 0; 189 | for (AbstractJsonType abstractJsonType : mergeWith.convertToListWithRealNull()) { 190 | addElement(index, abstractJsonType, currentPathMetadata); 191 | index++; 192 | } 193 | } 194 | } 195 | --------------------------------------------------------------------------------