Number to Integer/Long
От: vsb Казахстан  
Дата: 02.12.21 11:46
Оценка:
Хочу не прописывать конкретные типы в JSON-маппингах для большей гибкости. Но в коде, конечно, нужно конвертировать в конкретные типы. Для этого написал утилитные методы:

    public static int toIntExact(Number n) {
        requireNonNull(n, "n");
        if (n instanceof BigDecimal bd) {
            return bd.intValueExact();
        }
        if (n instanceof BigInteger bi) {
            return bi.intValueExact();
        }
        if (n instanceof Byte b) {
            return b.intValue();
        }
        if (n instanceof Float f) {
            return toIntExact(f.doubleValue());
        }
        if (n instanceof Double d) {
            return toIntExact(d.doubleValue());
        }
        if (n instanceof Integer i) {
            return i;
        }
        if (n instanceof Long l) {
            return Math.toIntExact(l);
        }
        if (n instanceof Short s) {
            return s.intValue();
        }
        throw new UnsupportedOperationException("Unsupported number class: " + n.getClass().getName());
    }

    public static Integer toIntegerExact(Number n) {
        if (n == null) {
            return null;
        }
        if (n instanceof Integer i) {
            return i;
        }
        return toIntExact(n);
    }

    public static int toIntExact(double d) {
        long l = Math.round(d);
        if (l != d) {
            throw new ArithmeticException("not int: " + d);
        }
        return Math.toIntExact(l);
    }

    public static long toLongValueExact(Number n) {
        requireNonNull(n, "n");
        if (n instanceof BigDecimal bd) {
            return bd.longValueExact();
        }
        if (n instanceof BigInteger bi) {
            return bi.longValueExact();
        }
        if (n instanceof Byte b) {
            return b.longValue();
        }
        if (n instanceof Float f) {
            return toLongValueExact(f.doubleValue());
        }
        if (n instanceof Double d) {
            return toLongValueExact(d.doubleValue());
        }
        if (n instanceof Integer i) {
            return i.longValue();
        }
        if (n instanceof Long l) {
            return l;
        }
        if (n instanceof Short s) {
            return s.longValue();
        }
        throw new UnsupportedOperationException("Unsupported number class: " + n.getClass().getName());
    }

    public static Long toLongExact(Number n) {
        if (n == null) {
            return null;
        }
        if (n instanceof Long l) {
            return l;
        }
        return toLongValueExact(n);
    }

    public static long toLongValueExact(double d) {
        long l = Math.round(d);
        if (l != d) {
            throw new ArithmeticException("not int: " + l);
        }
        return l;
    }


Но есть ощущение, что это уже кто-то написал в какой-нибудь популярной библиотеке, но сходу нигде не нашёл.

Number.intValue() не предлагать, оно не кидает ошибку при неверной конвертации, надо кидать.
Re: Number to Integer/Long
От: A13x США  
Дата: 02.12.21 22:37
Оценка: +1
Здравствуйте, vsb, Вы писали:

vsb>Хочу не прописывать конкретные типы в JSON-маппингах для большей гибкости. Но в коде, конечно, нужно конвертировать в конкретные типы. Для этого написал утилитные методы...


В таком виде выглядит бессмыссленно. Уж если хочется "exact value", toIntExact и прочие с ним должны оперировать с сериализованным представлением числа, чтобы выбрать его с наибольшей точностью и судя по названию должны кидать исключение если "exact" значения не удастся получить. По умолчанию число будет десериализовываться в double — впрочем, не могу сказать точно за все библиотеки — что очевидно уже не позволит получать "exact value" для достаточно больших long значений, даже если мантисса в сериализованном представлении содержит в точности все цифры позволяющие это точное значение получить в теории.
Re: Number to Integer/Long
От: rosencrantz США  
Дата: 02.12.21 22:40
Оценка:
Здравствуйте, vsb, Вы писали:

vsb>Хочу не прописывать конкретные типы в JSON-маппингах для большей гибкости. Но в коде, конечно, нужно конвертировать в конкретные типы.


Не помогу, но интересно зачем это нужно. Всегда использовал Jackson для сериализации JSON, всегда хватало.
Re[2]: Number to Integer/Long
От: vsb Казахстан  
Дата: 03.12.21 06:10
Оценка:
Здравствуйте, A13x, Вы писали:

A>По умолчанию число будет десериализовываться в double — впрочем, не могу сказать точно за все библиотеки — что очевидно уже не позволит получать "exact value" для достаточно больших long значений, даже если мантисса в сериализованном представлении содержит в точности все цифры позволяющие это точное значение получить в теории.


Я использую Jackson, он по умолчанию сериализует в Integer/Long/BigInteger/BigDecimal в зависимости от конкретного числа. Точней числа с плавающей точкой он по умолчанию сериализует в Double, но это легко настраивается.
Re[2]: Number to Integer/Long
От: vsb Казахстан  
Дата: 03.12.21 06:16
Оценка:
Здравствуйте, rosencrantz, Вы писали:

vsb>>Хочу не прописывать конкретные типы в JSON-маппингах для большей гибкости. Но в коде, конечно, нужно конвертировать в конкретные типы.


R>Не помогу, но интересно зачем это нужно. Всегда использовал Jackson для сериализации JSON, всегда хватало.


И я использую Jackson. Смысл в том, чтобы не прописывать в маппингах (которые по сути контракты) ненужные ограничения. В JSON нет типов Integer/Long/BigDecimal, там есть только строки и числа. Можно везде прописать BigDecimal, но это мне кажется не очень хорошей идеей, всё же обычно большинство чисел влезают в 32-битный int. Поэтому я прописываю везде Number, в setXxx передаю уже что угодно (любое число в Java конвертируется в Number), а при десериализации Jackson подставляет наиболее подходящую реализацию в зависимости от того, что реально пришло. А я её уже потом конвертирую в то число, которое, например, у меня в БД вмещается или в бизнес-логике используется.

Может, конечно, это и плохая идея, не знаю, пока существенных минусов не вижу.
Re[3]: Number to Integer/Long
От: halo Украина  
Дата: 03.12.21 08:22
Оценка: 12 (1)
Здравствуйте, vsb, Вы писали:

vsb>Я использую Jackson, он по умолчанию сериализует в Integer/Long/BigInteger/BigDecimal в зависимости от конкретного числа. Точней числа с плавающей точкой он по умолчанию сериализует в Double, но это легко настраивается.


В Gson для базового java.lang.Number используется своя простая реализация, com.google.gson.internal.LazilyParsedNumber, не имеющая представления о конкретном Java-типе, под капотом у которой просто лежит java.lang.String, но содержащая JSON-литерал именно в том виде, в каком он встретился в JSON-документе. Таким образом, выбор типа числа откладывается на потом. Кроме того, toString() тогда вообще возвращает исходный литерал, который на call site вообще можно преобразовывать во что угодно, не ограничиваясь настройками десериализатора.

Если по какой-то из причин не подходит контракт java.lang.Number, семейство "xxxValue"-методов которого даже ничего не говорит об исключениях (что, впрочем, не запрещает бросать непроверяемые исключения), может тогда есть смысл отталкиваться от своего типа, не расширяющего java.lang.Number (чтобы не было "xxxValue()"), реализуя в нём "xxxValueExact()"? Но и это не сильно отличается от десериализации тупо в строку, примерно так как это делает LazilyParsedNumber.

Мне кажется, что проблемы как таковой нет, и гибкость от внедрения java.lang.Number мнимая, примерно такая же как от использования java.lang.Object в DTO. Я бы не стал заниматься таким вообще, предпочитая прописывать конкретные типы, чтобы гарантировать поведение и соответствия значений в разных слоях приложения явно во избежание разного рода сюпризов.
Re[3]: Number to Integer/Long
От: Infernal Россия  
Дата: 07.01.22 17:21
Оценка:
Здравствуйте, vsb, Вы писали:

vsb>Здравствуйте, rosencrantz, Вы писали:


vsb>>>Хочу не прописывать конкретные типы в JSON-маппингах для большей гибкости. Но в коде, конечно, нужно конвертировать в конкретные типы.

R>>Не помогу, но интересно зачем это нужно. Всегда использовал Jackson для сериализации JSON, всегда хватало.
vsb>И я использую Jackson. Смысл в том, чтобы не прописывать в маппингах (которые по сути контракты) ненужные ограничения.
vsb>Может, конечно, это и плохая идея, не знаю, пока существенных минусов не вижу.

Примерно такую же задачу, решал при помощи аннотаций на поля в классе. Там прописываешь типы, ограничения, названия и прочие метаданные и т.д.
дальше это все в зависимости от аннотаций сериализуешь-десереализуешь из JSON просто по данным из этих аннотаций.

Или что мешает, смотреть на тип поля в вашем классе и в зависимости от него конвертировать?
Отредактировано 07.01.2022 17:30 Infernal . Предыдущая версия .
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.