Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions core/src/main/java/com/alibaba/fastjson2/JSONFactory.java
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,17 @@ public NameCacheEntry2(String name, long value0, long value1) {
'4', '5', '6', '7', '8', '9', '+', '/'
};

static final char[] CA_URL_SAFE = new char[]{
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n',
'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
'w', 'x', 'y', 'z', '0', '1', '2', '3',
'4', '5', '6', '7', '8', '9', '-', '_'
};

static final int[] DIGITS2 = new int[]{
+0, +0, +0, +0, +0, +0, +0, +0, +0, +0, +0, +0, +0, +0, +0, +0,
+0, +0, +0, +0, +0, +0, +0, +0, +0, +0, +0, +0, +0, +0, +0, +0,
Expand Down
30 changes: 25 additions & 5 deletions core/src/main/java/com/alibaba/fastjson2/JSONReader.java
Original file line number Diff line number Diff line change
Expand Up @@ -1748,6 +1748,10 @@ public byte[] readBinary() {
return Base64.getDecoder().decode(str);
}

if ((context.features & Feature.Base64URLSafeStringAsByteArray.mask) != 0) {
return Base64.getUrlDecoder().decode(str);
}

throw new JSONException(info("not support input " + str));
}

Expand Down Expand Up @@ -3133,11 +3137,17 @@ public byte[] readBase64() {
&& (p1 = str.indexOf(',', p0 + 1)) != -1 && str.regionMatches(p0 + 1, base64, 0, base64.length())) {
str = str.substring(p1 + 1);
}

if (str.isEmpty()) {
return new byte[0];
}

if ((context.features & Feature.Base64URLSafeStringAsByteArray.mask) != 0) {
return Base64.getUrlDecoder().decode(str);
}
return Base64.getDecoder().decode(str);
}
if (str.isEmpty()) {
return new byte[0];
}
return Base64.getDecoder().decode(str);
return null;
}

/**
Expand Down Expand Up @@ -6525,7 +6535,17 @@ public enum Feature {
* For example, ["value"] would be returned as "value".
* @since 2.0.60
*/
DisableStringArrayUnwrapping(1L << 34L);
DisableStringArrayUnwrapping(1L << 34L),

/**
* Feature that determines whether to treat Base64URL-encoded (URL-safe and unpadded) strings as byte arrays during deserialization.
* When enabled, strings that contain Base64URL-encoded data will be automatically decoded to byte arrays.
*
* <p>By default, this feature is disabled.</p>
*
* @since 2.0.62
*/
Base64URLSafeStringAsByteArray(1L << 35L);

public final long mask;

Expand Down
6 changes: 5 additions & 1 deletion core/src/main/java/com/alibaba/fastjson2/JSONReaderUTF8.java
Original file line number Diff line number Diff line change
Expand Up @@ -7669,7 +7669,11 @@ public final byte[] readBase64() {
}

byte[] src = Arrays.copyOfRange(bytes, offset, index);
decoded = Base64.getDecoder().decode(src);
if ((context.features & Feature.Base64URLSafeStringAsByteArray.mask) != 0) {
decoded = Base64.getUrlDecoder().decode(src);
} else {
decoded = Base64.getDecoder().decode(src);
}
} else {
decoded = new byte[0];
}
Expand Down
14 changes: 12 additions & 2 deletions core/src/main/java/com/alibaba/fastjson2/JSONWriter.java
Original file line number Diff line number Diff line change
Expand Up @@ -1168,7 +1168,7 @@ public void writeBinary(byte[] bytes) {
return;
}

if ((context.features & WriteByteArrayAsBase64.mask) != 0) {
if ((context.features & WriteByteArrayAsBase64.mask) != 0 || (context.features & WriteByteArrayAsBase64URLSafe.mask) != 0) {
writeBase64(bytes);
return;
}
Expand Down Expand Up @@ -4449,7 +4449,17 @@ public enum Feature {
*
* @since 2.0.61
*/
WriteFloatSpecialAsString(1L << 45);
WriteFloatSpecialAsString(1L << 45),

/**
* Feature that determines whether to write byte arrays as Base64URL-encoded (URL-safe and unpadded) strings during serialization.
* When enabled, byte array values will be serialized as Base64URL-encoded strings without padding rather than arrays of numbers or standard Base64.
*
* <p>By default, this feature is disabled.</p>
*
* @since 2.0.62
*/
WriteByteArrayAsBase64URLSafe(1L << 46);

public final long mask;

Expand Down
33 changes: 24 additions & 9 deletions core/src/main/java/com/alibaba/fastjson2/JSONWriterUTF16.java
Original file line number Diff line number Diff line change
Expand Up @@ -933,6 +933,9 @@ public final void writeBase64(byte[] bytes) {
return;
}

boolean urlSafe = (context.features & Feature.WriteByteArrayAsBase64URLSafe.mask) != 0;
char[] dict = urlSafe ? CA_URL_SAFE : CA;

int charsLen = ((bytes.length - 1) / 3 + 1) << 2; // base64 character count

int off = this.off;
Expand All @@ -948,10 +951,10 @@ public final void writeBase64(byte[] bytes) {
int i = (bytes[s++] & 0xff) << 16 | (bytes[s++] & 0xff) << 8 | (bytes[s++] & 0xff);

// Encode the int into four chars
chars[off] = CA[(i >>> 18) & 0x3f];
chars[off + 1] = CA[(i >>> 12) & 0x3f];
chars[off + 2] = CA[(i >>> 6) & 0x3f];
chars[off + 3] = CA[i & 0x3f];
chars[off] = dict[(i >>> 18) & 0x3f];
chars[off + 1] = dict[(i >>> 12) & 0x3f];
chars[off + 2] = dict[(i >>> 6) & 0x3f];
chars[off + 3] = dict[i & 0x3f];
off += 4;
}

Expand All @@ -962,11 +965,23 @@ public final void writeBase64(byte[] bytes) {
int i = ((bytes[eLen] & 0xff) << 10) | (left == 2 ? ((bytes[bytes.length - 1] & 0xff) << 2) : 0);

// Set last four chars
chars[off] = CA[i >> 12];
chars[off + 1] = CA[(i >>> 6) & 0x3f];
chars[off + 2] = left == 2 ? CA[i & 0x3f] : '=';
chars[off + 3] = '=';
off += 4;
chars[off] = dict[i >> 12];
chars[off + 1] = dict[(i >>> 6) & 0x3f];

if (urlSafe) {
// URL-safe Base64 without padding
if (left == 2) {
chars[off + 2] = dict[i & 0x3f];
off += 3;
} else {
off += 2;
}
} else {
// Standard Base64 includes padding
chars[off + 2] = left == 2 ? dict[i & 0x3f] : '=';
chars[off + 3] = '=';
off += 4;
}
}

chars[off] = quote;
Expand Down
38 changes: 29 additions & 9 deletions core/src/main/java/com/alibaba/fastjson2/JSONWriterUTF8.java
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,14 @@ public final void writeReference(String path) {

@Override
public final void writeBase64(byte[] value) {
if (value == null) {
writeArrayNull();
return;
}

boolean urlSafe = (context.features & Feature.WriteByteArrayAsBase64URLSafe.mask) != 0;
char[] dict = urlSafe ? CA_URL_SAFE : CA;

int charsLen = ((value.length - 1) / 3 + 1) << 2; // base64 character count

int off = this.off;
Expand All @@ -96,10 +104,10 @@ public final void writeBase64(byte[] value) {
int i = (value[s++] & 0xff) << 16 | (value[s++] & 0xff) << 8 | (value[s++] & 0xff);

// Encode the int into four chars
bytes[off] = (byte) CA[(i >>> 18) & 0x3f];
bytes[off + 1] = (byte) CA[(i >>> 12) & 0x3f];
bytes[off + 2] = (byte) CA[(i >>> 6) & 0x3f];
bytes[off + 3] = (byte) CA[i & 0x3f];
bytes[off] = (byte) dict[(i >>> 18) & 0x3f];
bytes[off + 1] = (byte) dict[(i >>> 12) & 0x3f];
bytes[off + 2] = (byte) dict[(i >>> 6) & 0x3f];
bytes[off + 3] = (byte) dict[i & 0x3f];
off += 4;
}

Expand All @@ -110,11 +118,23 @@ public final void writeBase64(byte[] value) {
int i = ((value[eLen] & 0xff) << 10) | (left == 2 ? ((value[value.length - 1] & 0xff) << 2) : 0);

// Set last four chars
bytes[off] = (byte) CA[i >> 12];
bytes[off + 1] = (byte) CA[(i >>> 6) & 0x3f];
bytes[off + 2] = left == 2 ? (byte) CA[i & 0x3f] : (byte) '=';
bytes[off + 3] = '=';
off += 4;
bytes[off] = (byte) dict[i >> 12];
bytes[off + 1] = (byte) dict[(i >>> 6) & 0x3f];

if (urlSafe) {
// URL-safe Base64 without padding
if (left == 2) {
bytes[off + 2] = (byte) dict[i & 0x3f];
off += 3;
} else {
off += 2;
}
} else {
// Standard Base64 includes padding
bytes[off + 2] = left == 2 ? (byte) dict[i & 0x3f] : (byte) '=';
bytes[off + 3] = '=';
off += 4;
}
}

bytes[off] = (byte) quote;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3885,6 +3885,11 @@ static ObjectReader getInitReader(
if (objectReader != ObjectReaderImplDate.INSTANCE) {
initReader = objectReader;
}
} else if (fieldClass == Instant.class) {
ObjectReader objectReader = provider.getObjectReader(Instant.class);
if (objectReader != ObjectReaderImplInstant.INSTANCE) {
initReader = objectReader;
}
}
}
return initReader;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import java.util.List;

import static com.alibaba.fastjson2.JSONReader.Feature.Base64StringAsByteArray;
import static com.alibaba.fastjson2.JSONReader.Feature.Base64URLSafeStringAsByteArray;

class ObjectReaderImplGenericArray
implements ObjectReader {
Expand Down Expand Up @@ -84,7 +85,8 @@ public Object readObject(JSONReader jsonReader, Type fieldType, Object fieldName
&& ((GenericArrayType) fieldType).getGenericComponentType() == byte.class
) {
byte[] bytes;
if ((jsonReader.features(features) & Base64StringAsByteArray.mask) != 0) {
if ((jsonReader.features(features) & Base64StringAsByteArray.mask) != 0
|| (jsonReader.features(features) & Base64URLSafeStringAsByteArray.mask) != 0) {
String str = jsonReader.readString();
bytes = Base64.getDecoder().decode(str);
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import java.util.function.Function;

import static com.alibaba.fastjson2.JSONReader.Feature.Base64StringAsByteArray;
import static com.alibaba.fastjson2.JSONReader.Feature.Base64URLSafeStringAsByteArray;

class ObjectReaderImplInt8ValueArray
extends ObjectReaderPrimitive {
Expand All @@ -33,7 +34,13 @@ class ObjectReaderImplInt8ValueArray
ObjectReaderImplInt8ValueArray(Function<byte[], Object> builder, String format) {
super(byte[].class);
this.format = format;
this.features = "base64".equals(format) ? Base64StringAsByteArray.mask : 0;
if ("base64".equals(format)) {
this.features = Base64StringAsByteArray.mask;
} else if ("base64url".equals(format)) {
this.features = Base64URLSafeStringAsByteArray.mask;
} else {
this.features = 0;
}
this.builder = builder;
}

Expand Down Expand Up @@ -75,7 +82,8 @@ public Object readObject(JSONReader jsonReader, Type fieldType, Object fieldName

if (jsonReader.isString()) {
byte[] bytes;
if ((jsonReader.features(this.features | features) & Base64StringAsByteArray.mask) != 0) {
if ((jsonReader.features(this.features | features) & Base64StringAsByteArray.mask) != 0
|| (jsonReader.features(this.features | features) & Base64URLSafeStringAsByteArray.mask) != 0) {
bytes = jsonReader.readBase64();
} else {
String str = jsonReader.readString();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -679,6 +679,14 @@ public Object readObject(JSONReader jsonReader, Type fieldType, Object fieldName
break;
}

if (jsonReader.current() == '/') {
jsonReader.skipComment();
}

if (jsonReader.nextIfMatch(']')) {
break;
}

Object item;
if (itemType == String.class) {
item = jsonReader.readString();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -600,7 +600,9 @@ public void writeBinary(JSONWriter jsonWriter, byte[] value) {
if ((features & WriteNonStringValueAsString.mask) != 0) {
jsonWriter.writeString(value);
} else if ("base64".equals(format)
|| (format == null && (jsonWriter.getFeatures(this.features) & WriteByteArrayAsBase64.mask) != 0)
|| (format == null &&
(jsonWriter.getFeatures(this.features) & WriteByteArrayAsBase64.mask) != 0 ||
(jsonWriter.getFeatures(this.features) & WriteByteArrayAsBase64URLSafe.mask) != 0)
) {
jsonWriter.writeBase64(value);
} else if ("hex".equals(format)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -365,16 +365,17 @@ private boolean writeInternal(JSONWriter jsonWriter, T object) {
}

Class<?> valueClass = value.getClass();
if (valueClass == byte[].class) {
writeBinary(jsonWriter, (byte[]) value);
return true;
}

ObjectWriter valueWriter = getObjectWriter(jsonWriter, valueClass);
if (valueWriter == null) {
throw new JSONException("get objectWriter error : " + valueClass);
}

if (valueClass == byte[].class
&& (valueWriter == ObjectWriterImplInt8Array.INSTANCE || valueWriter == ObjectWriterImplInt8ValueArray.INSTANCE)) {
writeBinary(jsonWriter, (byte[]) value);
return true;
}

if (unwrapped
&& writeWithUnwrapped(jsonWriter, value, features, refDetect, valueWriter)) {
return true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -451,17 +451,18 @@ boolean record = BeanUtils.isRecord(objectClass);
}

long writerFieldFeatures = features | beanFeatures;
boolean fieldBased = (writerFieldFeatures & JSONWriter.Feature.FieldBased.mask) != 0;
boolean fieldBased = ((writerFieldFeatures & JSONWriter.Feature.FieldBased.mask) != 0 && !objectClass.isInterface())
|| !beanInfo.alphabetic;
Comment on lines -454 to +455
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这里修复 creator=reflect 时,设置 alphabetic=false(序列化对象字段的输出顺序)不生效的问题;同时调整了其它地方,与 creator=asm 保持一致


if (fieldBased && (record || objectClass.isInterface())) {
if (fieldBased && record) {
fieldBased = false;
}

List<FieldWriter> fieldWriters;
Map<String, FieldWriter> fieldWriterMap = new LinkedHashMap<>();
final FieldInfo fieldInfo = new FieldInfo();

if (fieldBased) {
Map<String, FieldWriter> fieldWriterMap = new TreeMap<>();
BeanUtils.declaredFields(objectClass, field -> {
fieldInfo.init();
FieldWriter fieldWriter = createFieldWriter(objectClass, writerFieldFeatures, provider, beanInfo, fieldInfo, field);
Expand All @@ -484,8 +485,6 @@ boolean record = BeanUtils.isRecord(objectClass);
}

if (!fieldWritersCreated) {
Map<String, FieldWriter> fieldWriterMap = new LinkedHashMap<>();

if (!record) {
BeanUtils.declaredFields(objectClass, field -> {
fieldInfo.init();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import java.util.zip.GZIPOutputStream;

import static com.alibaba.fastjson2.JSONWriter.Feature.WriteByteArrayAsBase64;
import static com.alibaba.fastjson2.JSONWriter.Feature.WriteByteArrayAsBase64URLSafe;

final class ObjectWriterImplInt8ValueArray
extends ObjectWriterPrimitiveImpl {
Expand Down Expand Up @@ -96,6 +97,7 @@ public void write(JSONWriter jsonWriter, Object object, Object fieldName, Type f
if ("base64".equals(format)
|| "gzip,base64".equals(format)
|| (jsonWriter.getFeatures(features) & WriteByteArrayAsBase64.mask) != 0
|| (jsonWriter.getFeatures(features) & WriteByteArrayAsBase64URLSafe.mask) != 0
) {
jsonWriter.writeBase64(bytes);
return;
Expand Down
Loading
Loading