Làm cách nào để sử dụng Bộ nối tiếp tùy chỉnh với Jackson?


111

Tôi có hai lớp Java mà tôi muốn tuần tự hóa thành JSON bằng cách sử dụng Jackson:

public class User {
    public final int id;
    public final String name;

    public User(int id, String name) {
        this.id = id;
        this.name = name;
    }
}

public class Item {
    public final int id;
    public final String itemNr;
    public final User createdBy;

    public Item(int id, String itemNr, User createdBy) {
        this.id = id;
        this.itemNr = itemNr;
        this.createdBy = createdBy;
    }
}

Tôi muốn tuần tự hóa một Mục thành JSON này:

{"id":7, "itemNr":"TEST", "createdBy":3}

với Người dùng được tuần tự hóa để chỉ bao gồm id. Tôi cũng sẽ có thể sắp xếp tất cả các đối tượng người dùng thành JSON như:

{"id":3, "name": "Jonas", "email": "jonas@example.com"}

Vì vậy, tôi đoán rằng tôi cần viết một bộ tuần tự tùy chỉnh cho Itemvà thử với điều này:

public class ItemSerializer extends JsonSerializer<Item> {

@Override
public void serialize(Item value, JsonGenerator jgen,
        SerializerProvider provider) throws IOException,
        JsonProcessingException {
    jgen.writeStartObject();
    jgen.writeNumberField("id", value.id);
    jgen.writeNumberField("itemNr", value.itemNr);
    jgen.writeNumberField("createdBy", value.user.id);
    jgen.writeEndObject();
}

}

Tôi tuần tự hóa JSON với mã này từ Jackson Cách thực hiện: Bộ nối tiếp tùy chỉnh :

ObjectMapper mapper = new ObjectMapper();
SimpleModule simpleModule = new SimpleModule("SimpleModule", 
                                              new Version(1,0,0,null));
simpleModule.addSerializer(new ItemSerializer());
mapper.registerModule(simpleModule);
StringWriter writer = new StringWriter();
try {
    mapper.writeValue(writer, myItem);
} catch (JsonGenerationException e) {
    e.printStackTrace();
} catch (JsonMappingException e) {
    e.printStackTrace();
} catch (IOException e) {
    e.printStackTrace();
}

Nhưng tôi gặp lỗi này:

Exception in thread "main" java.lang.IllegalArgumentException: JsonSerializer of type com.example.ItemSerializer does not define valid handledType() (use alternative registration method?)
    at org.codehaus.jackson.map.module.SimpleSerializers.addSerializer(SimpleSerializers.java:62)
    at org.codehaus.jackson.map.module.SimpleModule.addSerializer(SimpleModule.java:54)
    at com.example.JsonTest.main(JsonTest.java:54)

Làm cách nào để sử dụng Bộ nối tiếp tùy chỉnh với Jackson?


Đây là cách tôi sẽ làm với Gson:

public class UserAdapter implements JsonSerializer<User> {

    @Override 
    public JsonElement serialize(User src, java.lang.reflect.Type typeOfSrc,
            JsonSerializationContext context) {
        return new JsonPrimitive(src.id);
    }
}

    GsonBuilder builder = new GsonBuilder();
    builder.registerTypeAdapter(User.class, new UserAdapter());
    Gson gson = builder.create();
    String json = gson.toJson(myItem);
    System.out.println("JSON: "+json);

Nhưng tôi cần làm điều đó với Jackson ngay bây giờ, vì Gson không có hỗ trợ cho các giao diện.


Làm thế nào / ở đâu bạn có được Jackson để sử dụng Serializer tùy chỉnh của bạn cho Item? Tôi đang gặp sự cố trong đó phương thức bộ điều khiển của tôi trả về một đối tượng được tuần tự hóa tiêu chuẩn TypeA, nhưng đối với một phương thức bộ điều khiển cụ thể khác, tôi muốn tuần tự hóa nó theo cách khác. Nó sẽ trông như thế nào?
Don Cheadle

Tôi đã viết một bài về Cách viết Bộ nối tiếp tùy chỉnh với Jackson có thể hữu ích cho một số người.
Sam Berry

Câu trả lời:


51

Như đã đề cập, @JsonValue là một cách tốt. Nhưng nếu bạn không bận tâm về một bộ nối tiếp tùy chỉnh, thì không cần phải viết một cái cho Item mà là một cái cho User - nếu vậy, nó sẽ đơn giản như:

public void serialize(Item value, JsonGenerator jgen,
    SerializerProvider provider) throws IOException,
    JsonProcessingException {
  jgen.writeNumber(id);
}

Tuy nhiên, một khả năng khác là thực hiện JsonSerializable, trong trường hợp đó không cần đăng ký.

Như lỗi; điều đó thật kỳ lạ - bạn có thể muốn nâng cấp lên phiên bản mới hơn. Nhưng cũng an toàn hơn khi mở rộng org.codehaus.jackson.map.ser.SerializerBasevì nó sẽ có các triển khai tiêu chuẩn của các phương thức không thiết yếu (tức là mọi thứ ngoại trừ lệnh gọi tuần tự hóa thực tế).


Với điều này, tôi nhận được cùng một lỗi:Exception in thread "main" java.lang.IllegalArgumentException: JsonSerializer of type com.example.JsonTest$UserSerilizer does not define valid handledType() (use alternative registration method?) at org.codehaus.jackson.map.module.SimpleSerializers.addSerializer(SimpleSerializers.java:62) at org.codehaus.jackson.map.module.SimpleModule.addSerializer(SimpleModule.java:54) at com.example.JsonTest.<init>(JsonTest.java:27) at com.exampple.JsonTest.main(JsonTest.java:102)
Jonas

Tôi sử dụng phiên bản ổn định mới nhất của Jacskson, 1.8.5.
Jonas

4
Cảm ơn. Tôi sẽ xem xét ... Ah! Nó thực sự đơn giản (mặc dù thông báo lỗi là không tốt) - bạn chỉ cần đăng ký bộ nối tiếp với phương thức khác, để chỉ định lớp mà bộ nối tiếp dành cho: nếu không, nó phải trả về lớp từ handleType (). Vì vậy, hãy sử dụng 'addSerializer' lấy JavaType hoặc Class làm đối số và nó sẽ hoạt động.
StaxMan

Điều gì sẽ xảy ra nếu điều này không được chạy?
Matej J

62

Bạn có thể đặt @JsonSerialize(using = CustomDateSerializer.class)bất kỳ trường ngày tháng nào của đối tượng sẽ được tuần tự hóa.

public class CustomDateSerializer extends SerializerBase<Date> {

    public CustomDateSerializer() {
        super(Date.class, true);
    }

    @Override
    public void serialize(Date value, JsonGenerator jgen, SerializerProvider provider)
        throws IOException, JsonProcessingException {
        SimpleDateFormat formatter = new SimpleDateFormat("EEE MMM dd yyyy HH:mm:ss 'GMT'ZZZ (z)");
        String format = formatter.format(value);
        jgen.writeString(format);
    }

}

1
đáng chú ý: sử dụng @JsonSerialize(contentUsing= ...)khi chú thích Bộ sưu tập (ví dụ @JsonSerialize(contentUsing= CustomDateSerializer.class) List<Date> dates)
coderatchet

33

Tôi cũng đã thử làm điều này và có một lỗi trong mã ví dụ trên trang web Jackson không bao gồm kiểu ( .class) trong lệnh gọi đến addSerializer()phương thức, sẽ đọc như thế này:

simpleModule.addSerializer(Item.class, new ItemSerializer());

Nói cách khác, đây là những dòng khởi tạo simpleModulevà thêm bộ nối tiếp (với dòng không chính xác trước đó được nhận xét):

ObjectMapper mapper = new ObjectMapper();
SimpleModule simpleModule = new SimpleModule("SimpleModule", 
                                          new Version(1,0,0,null));
// simpleModule.addSerializer(new ItemSerializer());
simpleModule.addSerializer(Item.class, new ItemSerializer());
mapper.registerModule(simpleModule);

FYI: Đây là tài liệu tham khảo cho mã ví dụ chính xác: http://wiki.fasterxml.com/JacksonFeatureModules


9

Sử dụng @JsonValue:

public class User {
    int id;
    String name;

    @JsonValue
    public int getId() {
        return id;
    }
}

@JsonValue chỉ hoạt động trên các phương thức nên bạn phải thêm phương thức getId. Bạn sẽ có thể bỏ qua hoàn toàn bộ tuần tự tùy chỉnh của mình.


2
Tôi nghĩ rằng điều này sẽ ảnh hưởng đến tất cả các nỗ lực tuần tự hóa Người dùng, khiến việc hiển thị tên Người dùng qua JSON trở nên khó khăn.
Paul M

Tôi không thể sử dụng giải pháp này, vì tôi cũng cần có thể tuần tự hóa tất cả các đối tượng người dùng với tất cả các trường. Và giải pháp này sẽ phá vỡ sự tuần tự hóa đó vì chỉ trường id mới được đưa vào. Không có cách nào để tạo bộ phân loại tùy chỉnh cho Jackson cũng như cho Gson?
Jonas

1
Bạn có thể nhận xét tại sao JSON Views (trong câu trả lời của tôi) không phù hợp với nhu cầu của bạn?
Paul M

@ người dùng: Nó có thể là một giải pháp tốt, tôi đang đọc về nó và đang thử.
Jonas

2
Cũng lưu ý rằng bạn có thể sử dụng @JsonSerialize (using = MySerializer.class) để chỉ định tuần tự hóa cụ thể cho thuộc tính của bạn (trường hoặc getter), vì vậy nó chỉ được sử dụng cho thuộc tính thành viên chứ KHÔNG phải tất cả các trường hợp của loại.
StaxMan

8

Tôi đã viết một ví dụ cho Timestamp.classtuần tự hóa / deserialization tùy chỉnh , nhưng bạn có thể sử dụng nó cho những gì bạn muốn.

Khi tạo đối tượng ánh xạ, hãy làm như sau:

public class JsonUtils {

    public static ObjectMapper objectMapper = null;

    static {
        objectMapper = new ObjectMapper();
        SimpleModule s = new SimpleModule();
        s.addSerializer(Timestamp.class, new TimestampSerializerTypeHandler());
        s.addDeserializer(Timestamp.class, new TimestampDeserializerTypeHandler());
        objectMapper.registerModule(s);
    };
}

ví dụ trong java eebạn có thể khởi tạo nó bằng cách này:

import java.time.LocalDateTime;

import javax.ws.rs.ext.ContextResolver;
import javax.ws.rs.ext.Provider;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;

@Provider
public class JacksonConfig implements ContextResolver<ObjectMapper> {

    private final ObjectMapper objectMapper;

    public JacksonConfig() {
        objectMapper = new ObjectMapper();
        SimpleModule s = new SimpleModule();
        s.addSerializer(Timestamp.class, new TimestampSerializerTypeHandler());
        s.addDeserializer(Timestamp.class, new TimestampDeserializerTypeHandler());
        objectMapper.registerModule(s);
    };

    @Override
    public ObjectMapper getContext(Class<?> type) {
        return objectMapper;
    }
}

nơi mà bộ nối tiếp phải giống như thế này:

import java.io.IOException;
import java.sql.Timestamp;

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;

public class TimestampSerializerTypeHandler extends JsonSerializer<Timestamp> {

    @Override
    public void serialize(Timestamp value, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonProcessingException {
        String stringValue = value.toString();
        if(stringValue != null && !stringValue.isEmpty() && !stringValue.equals("null")) {
            jgen.writeString(stringValue);
        } else {
            jgen.writeNull();
        }
    }

    @Override
    public Class<Timestamp> handledType() {
        return Timestamp.class;
    }
}

và deserializer một cái gì đó như thế này:

import java.io.IOException;
import java.sql.Timestamp;

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.SerializerProvider;

public class TimestampDeserializerTypeHandler extends JsonDeserializer<Timestamp> {

    @Override
    public Timestamp deserialize(JsonParser jp, DeserializationContext ds) throws IOException, JsonProcessingException {
        SqlTimestampConverter s = new SqlTimestampConverter();
        String value = jp.getValueAsString();
        if(value != null && !value.isEmpty() && !value.equals("null"))
            return (Timestamp) s.convert(Timestamp.class, value);
        return null;
    }

    @Override
    public Class<Timestamp> handledType() {
        return Timestamp.class;
    }
}

7

Đây là những mẫu hành vi mà tôi đã nhận thấy trong khi cố gắng tìm hiểu về Jackson tuần tự.

1) Giả sử có một đối tượng Lớp học và một lớp Học sinh. Tôi đã công khai mọi thứ và cuối cùng để dễ dàng.

public class Classroom {
    public final double double1 = 1234.5678;
    public final Double Double1 = 91011.1213;
    public final Student student1 = new Student();
}

public class Student {
    public final double double2 = 1920.2122;
    public final Double Double2 = 2324.2526;
}

2) Giả sử rằng đây là các trình tuần tự hóa mà chúng tôi sử dụng để tuần tự hóa các đối tượng thành JSON. WriteObjectField sử dụng trình tuần tự của chính đối tượng nếu nó được đăng ký với trình ánh xạ đối tượng; nếu không, thì nó tuần tự hóa nó như một POJO. WriteNumberField độc quyền chỉ chấp nhận các nguyên thủy làm đối số.

public class ClassroomSerializer extends StdSerializer<Classroom> {
    public ClassroomSerializer(Class<Classroom> t) {
        super(t);
    }

    @Override
    public void serialize(Classroom value, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonGenerationException {
        jgen.writeStartObject();
        jgen.writeObjectField("double1-Object", value.double1);
        jgen.writeNumberField("double1-Number", value.double1);
        jgen.writeObjectField("Double1-Object", value.Double1);
        jgen.writeNumberField("Double1-Number", value.Double1);
        jgen.writeObjectField("student1", value.student1);
        jgen.writeEndObject();
    }
}

public class StudentSerializer extends StdSerializer<Student> {
    public StudentSerializer(Class<Student> t) {
        super(t);
    }

    @Override
    public void serialize(Student value, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonGenerationException {
        jgen.writeStartObject();
        jgen.writeObjectField("double2-Object", value.double2);
        jgen.writeNumberField("double2-Number", value.double2);
        jgen.writeObjectField("Double2-Object", value.Double2);
        jgen.writeNumberField("Double2-Number", value.Double2);
        jgen.writeEndObject();
    }
}

3) Chỉ đăng ký DoubleSerializer với mẫu đầu ra DecimalFormat ###,##0.000, trong SimpleModule và đầu ra là:

{
  "double1" : 1234.5678,
  "Double1" : {
    "value" : "91,011.121"
  },
  "student1" : {
    "double2" : 1920.2122,
    "Double2" : {
      "value" : "2,324.253"
    }
  }
}

Bạn có thể thấy rằng tuần tự hóa POJO phân biệt giữa đôi và đôi, bằng cách sử dụng DoubleSerialzer cho đôi và sử dụng định dạng chuỗi thông thường cho đôi.

4) Đăng ký DoubleSerializer và ClassroomSerializer, không có StudentSerializer. Chúng ta mong đợi rằng đầu ra sao cho nếu chúng ta viết một double dưới dạng một đối tượng, nó sẽ hoạt động như một Double, và nếu chúng ta viết một Double dưới dạng một số, nó sẽ hoạt động như một double. Biến cá thể Student nên được viết dưới dạng POJO và tuân theo mẫu ở trên vì nó không đăng ký.

{
  "double1-Object" : {
    "value" : "1,234.568"
  },
  "double1-Number" : 1234.5678,
  "Double1-Object" : {
    "value" : "91,011.121"
  },
  "Double1-Number" : 91011.1213,
  "student1" : {
    "double2" : 1920.2122,
    "Double2" : {
      "value" : "2,324.253"
    }
  }
}

5) Đăng ký tất cả các serializers. Đầu ra là:

{
  "double1-Object" : {
    "value" : "1,234.568"
  },
  "double1-Number" : 1234.5678,
  "Double1-Object" : {
    "value" : "91,011.121"
  },
  "Double1-Number" : 91011.1213,
  "student1" : {
    "double2-Object" : {
      "value" : "1,920.212"
    },
    "double2-Number" : 1920.2122,
    "Double2-Object" : {
      "value" : "2,324.253"
    },
    "Double2-Number" : 2324.2526
  }
}

đúng như mong đợi.

Một lưu ý quan trọng khác: Nếu bạn có nhiều bộ tuần tự cho cùng một lớp được đăng ký với cùng một Mô-đun, thì Mô-đun sẽ chọn bộ tuần tự cho lớp đó được thêm gần đây nhất vào danh sách. Điều này không nên được sử dụng - nó gây nhầm lẫn và tôi không chắc chắn mức độ nhất quán của điều này

Đạo đức: nếu bạn muốn tùy chỉnh tuần tự hóa các nguyên thủy trong đối tượng của mình, bạn phải viết tuần tự hóa của riêng mình cho đối tượng. Bạn không thể dựa vào việc đăng nhiều kỳ POJO Jackson.


Làm cách nào để bạn đăng ký ClassroomSerializer để xử lý các sự kiện xảy ra trong Lớp học chẳng hạn?
Trismegistos

5

Lượt xem JSON của Jackson có thể là một cách đơn giản hơn để đạt được yêu cầu của bạn, đặc biệt nếu bạn có một số tính linh hoạt trong định dạng JSON của mình.

Nếu {"id":7, "itemNr":"TEST", "createdBy":{id:3}}là một biểu diễn chấp nhận được thì điều này sẽ rất dễ đạt được với rất ít mã.

Bạn sẽ chỉ chú thích trường tên của Người dùng là một phần của chế độ xem và chỉ định một chế độ xem khác trong yêu cầu tuần tự hóa của bạn (các trường không được chú thích sẽ được bao gồm theo mặc định)

Ví dụ: Xác định các chế độ xem:

public class Views {
    public static class BasicView{}
    public static class CompleteUserView{}
}

Chú thích Người dùng:

public class User {
    public final int id;

    @JsonView(Views.CompleteUserView.class)
    public final String name;

    public User(int id, String name) {
        this.id = id;
        this.name = name;
    }
}

Và tuần tự hóa yêu cầu một dạng xem không chứa trường bạn muốn ẩn (các trường không có chú thích được tuần tự hóa theo mặc định):

objectMapper.getSerializationConfig().withView(Views.BasicView.class);

Tôi thấy Jackson JSON Views khó sử dụng và không thể tìm được giải pháp tốt cho vấn đề này.
Jonas

Jonas - Tôi đã thêm một ví dụ. Tôi thấy các lượt xem là một giải pháp thực sự tốt để tuần tự hóa cùng một đối tượng theo những cách khác nhau.
Paul M

Cảm ơn vì một ví dụ tốt. Đây là giải pháp tốt nhất cho đến nay. Nhưng không có cách nào để nhận createdBydưới dạng giá trị thay vì dưới dạng đối tượng?
Jonas

setSerializationView()dường như không được dùng nữa nên tôi đã sử dụng mapper.viewWriter(JacksonViews.ItemView.class).writeValue(writer, myItem);thay thế.
Jonas

Tôi nghi ngờ nó bằng cách sử dụng jsonviews. Một giải pháp nhanh chóng và bẩn thỉu mà tôi đã sử dụng trước khi khám phá các chế độ xem chỉ là sao chép các thuộc tính mà tôi quan tâm vào Bản đồ, rồi tuần tự hóa bản đồ.
Paul M

5

Trong trường hợp của tôi (Spring 3.2.4 và Jackson 2.3.1), cấu hình XML cho bộ tuần tự hóa tùy chỉnh:

<mvc:annotation-driven>
    <mvc:message-converters register-defaults="false">
        <bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
            <property name="objectMapper">
                <bean class="org.springframework.http.converter.json.Jackson2ObjectMapperFactoryBean">
                    <property name="serializers">
                        <array>
                            <bean class="com.example.business.serializer.json.CustomObjectSerializer"/>
                        </array>
                    </property>
                </bean>
            </property>
        </bean>
    </mvc:message-converters>
</mvc:annotation-driven>

theo cách không giải thích được đã bị thứ gì đó ghi đè trở lại mặc định.

Điều này đã làm việc cho tôi:

CustomObject.java

@JsonSerialize(using = CustomObjectSerializer.class)
public class CustomObject {

    private Long value;

    public Long getValue() {
        return value;
    }

    public void setValue(Long value) {
        this.value = value;
    }
}

CustomObjectSerializer.java

public class CustomObjectSerializer extends JsonSerializer<CustomObject> {

    @Override
    public void serialize(CustomObject value, JsonGenerator jgen,
        SerializerProvider provider) throws IOException,JsonProcessingException {
        jgen.writeStartObject();
        jgen.writeNumberField("y", value.getValue());
        jgen.writeEndObject();
    }

    @Override
    public Class<CustomObject> handledType() {
        return CustomObject.class;
    }
}

Không cần cấu hình XML ( <mvc:message-converters>(...)</mvc:message-converters>) trong giải pháp của tôi.


1

Nếu yêu cầu duy nhất của bạn trong bộ tuần tự hóa tùy chỉnh là bỏ qua việc tuần tự hóa nametrường User, hãy đánh dấu trường đó là tạm thời . Jackson sẽ không tuần tự hóa hoặc deserialize tạm thời các trường .

[xem thêm: Tại sao Java có các trường tạm thời? ]


Tôi đánh dấu nó ở đâu? Trong- Userlớp? Nhưng tôi cũng sẽ tuần tự hóa tất cả các đối tượng người dùng. Ví dụ: đầu tiên chỉ tuần tự hóa tất cả items(chỉ userIdvới một tham chiếu đến đối tượng người dùng) và sau đó tuần tự hóa tất cả users. Trong trường hợp này, tôi không thể đánh dấu fiels trong User-class.
Jonas

Theo thông tin mới này, cách tiếp cận này sẽ không phù hợp với bạn. Có vẻ như Jackson đang tìm kiếm thêm thông tin cho phương thức serializer tùy chỉnh (handleType () cần ghi đè?)
Mike G

Có, nhưng không có thông tin gì về handledType()phương thức trong tài liệu mà tôi đã liên kết đến và khi Eclipse tạo các phương thức để triển khai không handledType()được tạo ra, vì vậy tôi bối rối.
Jonas

Tôi không chắc vì wiki mà bạn đã liên kết không tham chiếu đến nó, nhưng trong phiên bản 1.5.1 có một hàm handleType () và trường hợp ngoại lệ dường như phàn nàn rằng phương thức bị thiếu hoặc không hợp lệ (lớp cơ sở trả về null từ phương thức). jackson.codehaus.org/1.5.1/javadoc/org/codehaus/jackson/map/…
Mike G

1

Bạn phải ghi đè phương thức handleType và mọi thứ sẽ hoạt động

@Override
public Class<Item> handledType()
{
  return Item.class;
}

0

Vấn đề trong trường hợp của bạn là ItemSerializer thiếu phương thức handleType () cần được ghi đè khỏi JsonSerializer

    public class ItemSerializer extends JsonSerializer<Item> {

    @Override
    public void serialize(Item value, JsonGenerator jgen,
            SerializerProvider provider) throws IOException,
            JsonProcessingException {
        jgen.writeStartObject();
        jgen.writeNumberField("id", value.id);
        jgen.writeNumberField("itemNr", value.itemNr);
        jgen.writeNumberField("createdBy", value.user.id);
        jgen.writeEndObject();
    }

   @Override
   public Class<Item> handledType()
   {
    return Item.class;
   }
}

Do đó, bạn đang nhận được lỗi rõ ràng là handleType () không được xác định

Exception in thread "main" java.lang.IllegalArgumentException: JsonSerializer of type com.example.ItemSerializer does not define valid handledType() 

Hy vọng nó sẽ giúp một ai đó. Cảm ơn vì đã đọc câu trả lời của tôi.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.