Skip to content

Commit

Permalink
Merge branch '2.19' into fix4680
Browse files Browse the repository at this point in the history
  • Loading branch information
cowtowncoder committed Oct 2, 2024
2 parents 83af378 + 26bdfec commit c09b191
Show file tree
Hide file tree
Showing 10 changed files with 752 additions and 271 deletions.
6 changes: 3 additions & 3 deletions .github/workflows/codeql-analysis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,12 @@ jobs:

# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@461ef6c76dfe95d5c364de2f431ddbd31a417628 # v3.26.9
uses: github/codeql-action/init@e2b3eafc8d227b0241d48be5f425d47c2d750a13 # v3.26.10
with:
languages: ${{ matrix.language }}

- name: Autobuild
uses: github/codeql-action/autobuild@461ef6c76dfe95d5c364de2f431ddbd31a417628 # v3.26.9
uses: github/codeql-action/autobuild@e2b3eafc8d227b0241d48be5f425d47c2d750a13 # v3.26.10

- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@461ef6c76dfe95d5c364de2f431ddbd31a417628 # v3.26.9
uses: github/codeql-action/analyze@e2b3eafc8d227b0241d48be5f425d47c2d750a13 # v3.26.10
5 changes: 5 additions & 0 deletions release-notes/CREDITS-2.x
Original file line number Diff line number Diff line change
Expand Up @@ -1837,3 +1837,8 @@ Rikkarth (rikkarth@github)
Maxim Valeev (@MaximValeev)
* Reported #4508: Deserialized JsonAnySetter field in Kotlin data class is null
(2.18.1)


Lars Benedetto (@lbenedetto)
* Contributed #4676: Support other enum naming strategies than camelCase
(2.19.0)
3 changes: 3 additions & 0 deletions release-notes/VERSION-2.x
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ Project: jackson-databind

2.19.0 (not yet released)

#4676: Support other enum naming strategies than camelCase
(requested by @hajdamak)
(contributed by Lars B)
#4680: Custom key deserialiser registered for Object.class is ignored on nested JSON
(reported by @devdanylo)
(fix by Joo-Hyuk K)
Expand Down
463 changes: 405 additions & 58 deletions src/main/java/com/fasterxml/jackson/databind/EnumNamingStrategies.java

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import com.fasterxml.jackson.databind.introspect.AnnotatedField;
import com.fasterxml.jackson.databind.introspect.AnnotatedMethod;
import com.fasterxml.jackson.databind.introspect.AnnotatedParameter;
import com.fasterxml.jackson.databind.util.NamingStrategyImpls;

/**
* Container for standard {@link PropertyNamingStrategy} implementations
Expand Down Expand Up @@ -124,42 +125,6 @@ public String nameForConstructorParameter(MapperConfig<?> config, AnnotatedParam
}

public abstract String translate(String propertyName);

/**
* Helper method to share implementation between snake and dotted case.
*/
protected String translateLowerCaseWithSeparator(final String input, final char separator)
{
if (input == null || input.isEmpty()) {
return input;
}

final int length = input.length();
final StringBuilder result = new StringBuilder(length + (length >> 1));
int upperCount = 0;
for (int i = 0; i < length; ++i) {
char ch = input.charAt(i);
char lc = Character.toLowerCase(ch);

if (lc == ch) { // lower-case letter means we can get new word
// but need to check for multi-letter upper-case (acronym), where assumption
// is that the last upper-case char is start of a new word
if (upperCount > 1) {
// so insert hyphen before the last character now
result.insert(result.length() - 1, separator);
}
upperCount = 0;
} else {
// Otherwise starts new word, unless beginning of string
if ((upperCount == 0) && (i > 0)) {
result.append(separator);
}
++upperCount;
}
result.append(lc);
}
return result.toString();
}
}

/*
Expand Down Expand Up @@ -230,35 +195,7 @@ public static class SnakeCaseStrategy extends NamingBase
@Override
public String translate(String input)
{
if (input == null) return input; // garbage in, garbage out
int length = input.length();
StringBuilder result = new StringBuilder(length * 2);
int resultLength = 0;
boolean wasPrevTranslated = false;
for (int i = 0; i < length; i++)
{
char c = input.charAt(i);
if (i > 0 || c != '_') // skip first starting underscore
{
if (Character.isUpperCase(c))
{
if (!wasPrevTranslated && resultLength > 0 && result.charAt(resultLength - 1) != '_')
{
result.append('_');
resultLength++;
}
c = Character.toLowerCase(c);
wasPrevTranslated = true;
}
else
{
wasPrevTranslated = false;
}
result.append(c);
resultLength++;
}
}
return resultLength > 0 ? result.toString() : input;
return NamingStrategyImpls.SNAKE_CASE.translate(input);
}
}

Expand All @@ -281,11 +218,7 @@ public static class UpperSnakeCaseStrategy extends SnakeCaseStrategy

@Override
public String translate(String input) {
String output = super.translate(input);
if (output == null) {
return null;
}
return output.toUpperCase();
return NamingStrategyImpls.UPPER_SNAKE_CASE.translate(input);
}
}

Expand All @@ -305,7 +238,7 @@ public static class LowerCamelCaseStrategy extends NamingBase

@Override
public String translate(String input) {
return input;
return NamingStrategyImpls.LOWER_CAMEL_CASE.translate(input);
}
}

Expand Down Expand Up @@ -343,18 +276,7 @@ public static class UpperCamelCaseStrategy extends NamingBase
*/
@Override
public String translate(String input) {
if (input == null || input.isEmpty()){
return input; // garbage in, garbage out
}
// Replace first lower-case letter with upper-case equivalent
char c = input.charAt(0);
char uc = Character.toUpperCase(c);
if (c == uc) {
return input;
}
StringBuilder sb = new StringBuilder(input);
sb.setCharAt(0, uc);
return sb.toString();
return NamingStrategyImpls.UPPER_CAMEL_CASE.translate(input);
}
}

Expand All @@ -376,10 +298,7 @@ public static class LowerCaseStrategy extends NamingBase

@Override
public String translate(String input) {
if (input == null || input.isEmpty()) {
return input;
}
return input.toLowerCase();
return NamingStrategyImpls.LOWER_CASE.translate(input);
}
}

Expand All @@ -401,7 +320,7 @@ public static class KebabCaseStrategy extends NamingBase

@Override
public String translate(String input) {
return translateLowerCaseWithSeparator(input, '-');
return NamingStrategyImpls.KEBAB_CASE.translate(input);
}
}

Expand All @@ -422,7 +341,7 @@ public static class LowerDotCaseStrategy extends NamingBase

@Override
public String translate(String input){
return translateLowerCaseWithSeparator(input, '.');
return NamingStrategyImpls.LOWER_DOT_CASE.translate(input);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
package com.fasterxml.jackson.databind.util;

/**
* Container for standard naming strategy implementations, specifically
* used by property naming strategies (see {@link com.fasterxml.jackson.databind.PropertyNamingStrategies})
* and enum naming strategies (see {@link com.fasterxml.jackson.databind.EnumNamingStrategies}).
*/
public enum NamingStrategyImpls {
/**
* beanName -> beanName
*/
LOWER_CAMEL_CASE {
@Override
public String translate(String beanName) {
return beanName; // beanName is already in lower camel case
}
},

/**
* beanName -> BeanName
*/
UPPER_CAMEL_CASE {
@Override
public String translate(String beanName) {
if (beanName == null || beanName.isEmpty()) {
return beanName; // garbage in, garbage out
}
// Replace first lower-case letter with upper-case equivalent
char c = beanName.charAt(0);
char uc = Character.toUpperCase(c);
if (c == uc) {
return beanName;
}
StringBuilder sb = new StringBuilder(beanName);
sb.setCharAt(0, uc);
return sb.toString();
}
},

/**
* beanName -> bean_name
*/
SNAKE_CASE {
@Override
public String translate(String beanName) {
if (beanName == null) return beanName; // garbage in, garbage out
int length = beanName.length();
StringBuilder result = new StringBuilder(length * 2);
int resultLength = 0;
boolean wasPrevTranslated = false;
for (int i = 0; i < length; i++) {
char c = beanName.charAt(i);
if (i > 0 || c != '_') // skip first starting underscore
{
if (Character.isUpperCase(c)) {
if (!wasPrevTranslated && resultLength > 0 && result.charAt(resultLength - 1) != '_') {
result.append('_');
resultLength++;
}
c = Character.toLowerCase(c);
wasPrevTranslated = true;
} else {
wasPrevTranslated = false;
}
result.append(c);
resultLength++;
}
}
return resultLength > 0 ? result.toString() : beanName;
}
},

/**
* beanName -> BEAN_NAME
*/
UPPER_SNAKE_CASE {
@Override
public String translate(String beanName) {
String output = SNAKE_CASE.translate(beanName);
if (output == null) {
return null;
}
return output.toUpperCase();
}
},

/**
* beanName -> beanname
*/
LOWER_CASE {
@Override
public String translate(String beanName) {
if (beanName == null || beanName.isEmpty()) {
return beanName;
}
return beanName.toLowerCase();
}
},

/**
* beanName -> bean-name
*/
KEBAB_CASE {
@Override
public String translate(String beanName) {
return translateLowerCaseWithSeparator(beanName, '-');
}
},

/**
* beanName -> bean.name
*/
LOWER_DOT_CASE {
@Override
public String translate(String beanName) {
return translateLowerCaseWithSeparator(beanName, '.');
}
},
;

public abstract String translate(final String beanName);

/**
* Helper method to share implementation between snake and dotted case.
*/
static String translateLowerCaseWithSeparator(final String beanName, final char separator) {
if (beanName == null || beanName.isEmpty()) {
return beanName;
}

final int length = beanName.length();
final StringBuilder result = new StringBuilder(length + (length >> 1));
int upperCount = 0;
for (int i = 0; i < length; ++i) {
char ch = beanName.charAt(i);
char lc = Character.toLowerCase(ch);

if (lc == ch) { // lower-case letter means we can get new word
// but need to check for multi-letter upper-case (acronym), where assumption
// is that the last upper-case char is start of a new word
if (upperCount > 1) {
// so insert hyphen before the last character now
result.insert(result.length() - 1, separator);
}
upperCount = 0;
} else {
// Otherwise starts new word, unless beginning of string
if ((upperCount == 0) && (i > 0)) {
result.append(separator);
}
++upperCount;
}
result.append(lc);
}
return result.toString();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package com.fasterxml.jackson.databind.tofix;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonValue;
import com.fasterxml.jackson.databind.testutil.DatabindTestUtil;
import com.fasterxml.jackson.databind.testutil.failure.JacksonTestFailureExpected;

import org.junit.jupiter.api.Test;

// [databind#4724] Deserialization behavior change with Java Records, JsonCreator and JsonValue between 2.17.2 => 2.18.0
public class RecordJsonCreatorAndJsonValue4724Test
extends DatabindTestUtil
{

public record Something(String value) {
public Something {
if (value == null || value.isEmpty()) {
throw new IllegalArgumentException("Value cannot be null or empty");
}
}

@JsonCreator
public static Something of(String value) {
if (value.isEmpty()) {
return null;
}
return new Something(value);
}

@Override
@JsonValue
public String toString() {
return value;
}
}

@JacksonTestFailureExpected
@Test
void deserialization() throws Exception {
newJsonMapper().readValue("\"\"", Something.class);
}

}
Loading

0 comments on commit c09b191

Please sign in to comment.