Skip to content

Commit

Permalink
support JSONType#rootName
Browse files Browse the repository at this point in the history
  • Loading branch information
wenshao committed Jul 3, 2024
1 parent 5b3544f commit 88a4f7c
Show file tree
Hide file tree
Showing 15 changed files with 316 additions and 6 deletions.
17 changes: 17 additions & 0 deletions core/src/main/java/com/alibaba/fastjson2/JSONObject.java
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,23 @@ public Object getOrDefault(Object key, Object defaultValue) {
);
}

/**
* @since 2.0.52
* @param key
* @param action
*/
public void forEchArrayObject(String key, Consumer<JSONObject> action) {
JSONArray array = getJSONArray(key);
if (array == null) {
return;
}

for (int i = 0; i < array.size(); i++) {
action.accept(
array.getJSONObject(i));
}
}

/**
* Returns the {@link JSONArray} of the associated keys in this {@link JSONObject}.
*
Expand Down
10 changes: 10 additions & 0 deletions core/src/main/java/com/alibaba/fastjson2/annotation/JSONType.java
Original file line number Diff line number Diff line change
Expand Up @@ -84,4 +84,14 @@
* @since 2.0.50
*/
boolean disableReferenceDetect() default false;

/**
* Similar to {@code javax.xml.bind.annotation.XmlRootElement},
* used to indicate name to use for root-level wrapping, if wrapping is
* enabled. Annotation itself does not indicate that wrapping should
* be used; but if it is, the name used for serialization should be the
* name specified here, and deserializer will expect the name as well.
* @since 2.0.52
*/
String rootName() default "";
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ public class BeanInfo {
public String objectWriterFieldName;
public String objectReaderFieldName;
public Class<? extends JSONReader.AutoTypeBeforeHandler> autoTypeBeforeHandler;
public String rootName;

public BeanInfo() {
if (JSONFactory.isDisableAutoType()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,15 @@ default T readArrayMappingObject(JSONReader jsonReader, Type fieldType, Object f
throw new UnsupportedOperationException();
}

/**
* @return {@link T}
*/
default T readObject(String str, JSONReader.Feature... features) {
try (JSONReader jsonReader = JSONReader.of(str, JSONFactory.createReadContext(features))) {
return readObject(jsonReader, null, null, getFeatures());
}
}

/**
* @return {@link T}
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -444,6 +444,13 @@ void getBeanInfo1x(BeanInfo beanInfo, Annotation annotation) {
}
break;
}
case "rootName": {
String rootName = (String) result;
if (!rootName.isEmpty()) {
beanInfo.rootName = rootName;
}
break;
}
case "naming": {
Enum naming = (Enum) result;
beanInfo.namingStrategy = naming.name();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -780,6 +780,27 @@ public <T> ObjectReader<T> createObjectReader(
Supplier<T> defaultCreator,
Function buildFunction,
FieldReader... fieldReaders
) {
return createObjectReader(
objectClass,
typeKey,
null,
features,
schema,
defaultCreator,
buildFunction,
fieldReaders);
}

public <T> ObjectReader<T> createObjectReader(
Class<T> objectClass,
String typeKey,
String rootName,
long features,
JSONSchema schema,
Supplier<T> defaultCreator,
Function buildFunction,
FieldReader... fieldReaders
) {
if (objectClass != null) {
int modifiers = objectClass.getModifiers();
Expand All @@ -788,10 +809,27 @@ public <T> ObjectReader<T> createObjectReader(
}
}

if (rootName != null) {
return new ObjectReaderRootName(
objectClass,
typeKey,
null,
rootName,
features,
schema,
defaultCreator,
buildFunction,
null,
null,
null,
fieldReaders);
}
switch (fieldReaders.length) {
case 1:
return new ObjectReader1(
objectClass,
null,
null,
features,
schema,
defaultCreator,
Expand Down Expand Up @@ -1248,6 +1286,7 @@ boolean record = BeanUtils.isRecord(objectClass);
ObjectReader<T> objectReader = createObjectReader(
objectClass,
beanInfo.typeKey,
beanInfo.rootName,
beanInfo.readerFeatures,
jsonSchema,
creator,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -284,7 +284,9 @@ public <T> ObjectReader<T> createObjectReader(
}
}

if (match && beanInfo.schema != null && !beanInfo.schema.isEmpty()) {
if (match
&& (beanInfo.rootName != null
|| (beanInfo.schema != null && !beanInfo.schema.isEmpty()))) {
match = false;
}

Expand Down Expand Up @@ -335,9 +337,11 @@ public <T> ObjectReader<T> createObjectReader(
);
}

@Override
public <T> ObjectReader<T> createObjectReader(
Class<T> objectClass,
String typeKey,
String rootName,
long features,
JSONSchema schema,
Supplier<T> defaultCreator,
Expand Down Expand Up @@ -373,6 +377,7 @@ public <T> ObjectReader<T> createObjectReader(
return super.createObjectReader(
objectClass,
typeKey,
rootName,
features,
schema,
defaultCreator,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
package com.alibaba.fastjson2.reader;

import com.alibaba.fastjson2.JSONException;
import com.alibaba.fastjson2.JSONReader;
import com.alibaba.fastjson2.schema.JSONSchema;
import com.alibaba.fastjson2.util.Fnv;

import java.lang.reflect.Type;
import java.util.Map;
import java.util.function.Function;
import java.util.function.Supplier;

public final class ObjectReaderRootName<T>
extends ObjectReaderAdapter<T> {
protected final String rootName;
protected final long rootNameHashCode;

public ObjectReaderRootName(
Class objectClass,
String typeKey,
String typeName,
String rootName,
long features,
JSONSchema schema,
Supplier creator,
Function buildFunction,
Class[] seeAlso,
String[] seeAlsoNames,
Class seeAlsoDefault,
FieldReader[] fieldReaders) {
super(
objectClass,
typeKey,
typeName,
features,
schema,
creator,
buildFunction,
seeAlso,
seeAlsoNames,
seeAlsoDefault,
fieldReaders
);
this.rootName = rootName;
this.rootNameHashCode = rootName == null ? 0 : Fnv.hashCode64(rootName);
}

public T createInstance(Map map, long features) {
Map object = (Map) map.get(rootName);
if (object == null) {
return null;
}
return super.createInstance(object, features);
}

@Override
public T readJSONBObject(JSONReader jsonReader, Type fieldType, Object fieldName, long features) {
if (jsonReader.nextIfNullOrEmptyString()) {
return null;
}
boolean objectStart = jsonReader.nextIfObjectStart();
if (!objectStart) {
throw new JSONException(jsonReader.info("read rootName error " + typeName));
}

T object = null;
for (int i = 0; ; i++) {
if (jsonReader.nextIfObjectEnd()) {
break;
}
if (rootNameHashCode == jsonReader.readFieldNameHashCode()) {
object = super.readJSONBObject(jsonReader, fieldType, fieldName, features);
} else {
jsonReader.skipValue();
}
}
return object;
}

@Override
public T readObject(JSONReader jsonReader, Type fieldType, Object fieldName, long features) {
if (jsonReader.nextIfNullOrEmptyString()) {
return null;
}
boolean objectStart = jsonReader.nextIfObjectStart();
if (!objectStart) {
throw new JSONException(jsonReader.info("read rootName error " + typeName));
}

T object = null;
for (int i = 0; ; i++) {
if (jsonReader.nextIfObjectEnd()) {
break;
}
if (rootNameHashCode == jsonReader.readFieldNameHashCode()) {
object = super.readObject(jsonReader, fieldType, fieldName, features);
} else {
jsonReader.skipValue();
}
}
return object;
}
}
7 changes: 7 additions & 0 deletions core/src/main/java/com/alibaba/fastjson2/util/BeanUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -2626,6 +2626,13 @@ public static void processJSONType1x(BeanInfo beanInfo, Annotation jsonType1x, M
}
break;
}
case "rootName": {
String rootName = (String) result;
if (!rootName.isEmpty()) {
beanInfo.rootName = rootName;
}
break;
}
case "alphabetic": {
Boolean alphabetic = (Boolean) result;
if (!alphabetic) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -135,9 +135,10 @@ default void write(JSONWriter jsonWriter, Object object) {
}

default String toJSONString(T object, JSONWriter.Feature... features) {
JSONWriter jsonWriter = JSONWriter.of(features);
write(jsonWriter, object, null, null, 0);
return jsonWriter.toString();
try (JSONWriter jsonWriter = JSONWriter.of(features)) {
write(jsonWriter, object, null, null, 0);
return jsonWriter.toString();
}
}

void write(JSONWriter jsonWriter, Object object, Object fieldName, Type fieldType, long features);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,11 @@ public void getBeanInfo(BeanInfo beanInfo, Class objectClass) {
if (jsonType.writeEnumAsJavaBean()) {
beanInfo.writeEnumAsJavaBean = true;
}

String rootName = jsonType.rootName();
if (!rootName.isEmpty()) {
beanInfo.rootName = rootName;
}
} else if (jsonType1x != null) {
final Annotation annotation = jsonType1x;
BeanUtils.annotationMethods(jsonType1x.annotationType(), method -> BeanUtils.processJSONType1x(beanInfo, annotation, method));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -516,7 +516,7 @@ boolean record = BeanUtils.isRecord(objectClass);
googleCollection =
"com.google.common.collect.AbstractMapBasedMultimap$RandomAccessWrappedList".equals(typeName)
|| "com.google.common.collect.AbstractMapBasedMultimap$WrappedSet".equals(typeName);
if (!googleCollection) {
if (!googleCollection && beanInfo.rootName == null) {
switch (fieldWriters.size()) {
case 1:
if ((fieldWriters.get(0).features & FieldInfo.VALUE_MASK) == 0) {
Expand Down Expand Up @@ -561,7 +561,11 @@ boolean record = BeanUtils.isRecord(objectClass);
}
}
if (writerAdapter == null) {
writerAdapter = new ObjectWriterAdapter(objectClass, beanInfo.typeKey, beanInfo.typeName, writerFeatures, fieldWriters);
if (beanInfo.rootName != null) {
writerAdapter = new ObjectWriterRootName(objectClass, beanInfo.typeKey, beanInfo.typeName, beanInfo.rootName, writerFeatures, fieldWriters);
} else {
writerAdapter = new ObjectWriterAdapter(objectClass, beanInfo.typeKey, beanInfo.typeName, writerFeatures, fieldWriters);
}
}

if (beanInfo.serializeFilters != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,7 @@ public ObjectWriter createObjectWriter(

if (Throwable.class.isAssignableFrom(objectClass)
|| BeanUtils.isExtendedMap(objectClass)
|| beanInfo.rootName != null
) {
return super.createObjectWriter(objectClass, features, provider);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package com.alibaba.fastjson2.writer;

import com.alibaba.fastjson2.JSONObject;
import com.alibaba.fastjson2.JSONWriter;

import java.lang.reflect.Type;
import java.util.List;

public final class ObjectWriterRootName<T>
extends ObjectWriterAdapter<T> {
final String rootName;
public ObjectWriterRootName(
Class<T> objectClass,
String typeKey,
String typeName,
String rootName,
long features,
List<FieldWriter> fieldWriters
) {
super(objectClass, typeKey, typeName, features, fieldWriters);
this.rootName = rootName;
}

public void writeJSONB(JSONWriter jsonWriter, Object object, Object fieldName, Type fieldType, long features) {
jsonWriter.startObject();
jsonWriter.writeName(rootName);
super.writeJSONB(jsonWriter, object, fieldName, fieldType, features);
jsonWriter.endObject();
}

public void write(JSONWriter jsonWriter, Object object, Object fieldName, Type fieldType, long features) {
jsonWriter.startObject();
jsonWriter.writeName(rootName);
jsonWriter.writeColon();
super.write(jsonWriter, object, fieldName, fieldType, features);
jsonWriter.endObject();
}

public JSONObject toJSONObject(T object, long features) {
return JSONObject.of(
rootName,
super.toJSONObject(object, features));
}
}
Loading

0 comments on commit 88a4f7c

Please sign in to comment.