Skip to content

Commit

Permalink
Fix #1702: Gson.toJson creates CharSequence which does not implement …
Browse files Browse the repository at this point in the history
…toString (#1703)

* Gson.toJson creates CharSequence which does not implement toString

* Improve Streams.AppendableWriter.CurrentWrite test

* Make setChars package-private
  • Loading branch information
Marcono1234 authored Aug 3, 2022
1 parent 4552db2 commit a1d2ebc
Show file tree
Hide file tree
Showing 2 changed files with 71 additions and 12 deletions.
22 changes: 18 additions & 4 deletions gson/src/main/java/com/google/gson/internal/Streams.java
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ private static final class AppendableWriter extends Writer {
}

@Override public void write(char[] chars, int offset, int length) throws IOException {
currentWrite.chars = chars;
currentWrite.setChars(chars);
appendable.append(currentWrite, offset, offset + length);
}

Expand Down Expand Up @@ -122,8 +122,15 @@ private static final class AppendableWriter extends Writer {
/**
* A mutable char sequence pointing at a single char[].
*/
static class CurrentWrite implements CharSequence {
char[] chars;
private static class CurrentWrite implements CharSequence {
private char[] chars;
private String cachedString;

void setChars(char[] chars) {
this.chars = chars;
this.cachedString = null;
}

@Override public int length() {
return chars.length;
}
Expand All @@ -133,7 +140,14 @@ static class CurrentWrite implements CharSequence {
@Override public CharSequence subSequence(int start, int end) {
return new String(chars, start, end - start);
}

// Must return string representation to satisfy toString() contract
@Override public String toString() {
if (cachedString == null) {
cachedString = new String(chars);
}
return cachedString;
}
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -20,18 +20,17 @@
import com.google.gson.JsonStreamParser;
import com.google.gson.JsonSyntaxException;
import com.google.gson.common.TestTypes.BagOfPrimitives;

import com.google.gson.reflect.TypeToken;
import java.util.Map;
import junit.framework.TestCase;

import java.io.CharArrayReader;
import java.io.CharArrayWriter;
import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
import java.io.StringWriter;
import java.io.Writer;
import java.util.Arrays;
import java.util.Map;
import junit.framework.TestCase;

/**
* Functional tests for the support of {@link Reader}s and {@link Writer}s.
Expand Down Expand Up @@ -89,8 +88,8 @@ public void testTopLevelNullObjectDeserializationWithReaderAndSerializeNulls() {
}

public void testReadWriteTwoStrings() throws IOException {
Gson gson= new Gson();
CharArrayWriter writer= new CharArrayWriter();
Gson gson = new Gson();
CharArrayWriter writer = new CharArrayWriter();
writer.write(gson.toJson("one").toCharArray());
writer.write(gson.toJson("two").toCharArray());
CharArrayReader reader = new CharArrayReader(writer.toCharArray());
Expand All @@ -102,8 +101,8 @@ public void testReadWriteTwoStrings() throws IOException {
}

public void testReadWriteTwoObjects() throws IOException {
Gson gson= new Gson();
CharArrayWriter writer= new CharArrayWriter();
Gson gson = new Gson();
CharArrayWriter writer = new CharArrayWriter();
BagOfPrimitives expectedOne = new BagOfPrimitives(1, 1, true, "one");
writer.write(gson.toJson(expectedOne).toCharArray());
BagOfPrimitives expectedTwo = new BagOfPrimitives(2, 2, false, "two");
Expand Down Expand Up @@ -132,4 +131,50 @@ public void testTypeMismatchThrowsJsonSyntaxExceptionForReaders() {
} catch (JsonSyntaxException expected) {
}
}

/**
* Verifies that passing an {@link Appendable} which is not an instance of {@link Writer}
* to {@code Gson.toJson} works correctly.
*/
public void testToJsonAppendable() {
class CustomAppendable implements Appendable {
final StringBuilder stringBuilder = new StringBuilder();
int toStringCallCount = 0;

@Override
public Appendable append(char c) throws IOException {
stringBuilder.append(c);
return this;
}

@Override
public Appendable append(CharSequence csq) throws IOException {
if (csq == null) {
csq = "null"; // Requirement by Writer.append
}
append(csq, 0, csq.length());
return this;
}

@Override
public Appendable append(CharSequence csq, int start, int end) throws IOException {
if (csq == null) {
csq = "null"; // Requirement by Writer.append
}

// According to doc, toString() must return string representation
String s = csq.toString();
toStringCallCount++;
stringBuilder.append(s, start, end);
return this;
}
}

CustomAppendable appendable = new CustomAppendable();
gson.toJson(Arrays.asList("test", 123, true), appendable);
// Make sure CharSequence.toString() was called at least two times to verify that
// CurrentWrite.cachedString is properly overwritten when char array changes
assertTrue(appendable.toStringCallCount >= 2);
assertEquals("[\"test\",123,true]", appendable.stringBuilder.toString());
}
}

0 comments on commit a1d2ebc

Please sign in to comment.